playLab.Hudson — Draft 2

Ashley Louie
Data Mining the City
8 min readOct 17, 2018

by Richard Chou, Ashley Louie, David Mauricio

An interactive model of the City of Hudson surveys the users’ programmatic priorities and simulates the amount of pedestrian activity by counting specific demographics.

Concept Sketch of Interactive Canvas

Intro
The City of Hudson faces challenges in equal accessibility, social infrastructure, and economic vitality. As part of our urban design research, we engage community stakeholders to better understand their programmatic interests for city improvements. Hudson lacks the resources and capital to invest in market and civic research for new developments. We hope to learn how different populations may react with different program, while assessing user preferences for desirable program to add.

playLab.Hudson

Model — The model simulates pedestrian street activity relative to weighted accessibility program.

The Model

Individuals (Agents) — The agents include low-income residents, high-income residents, and visitors, which are three of the significant actors in Hudson’s community.

Environment — The environment shows a few blocks of downtown, where users can change the environment by toggling the time of day, deploying program, and adjust parameters for program attractions. The model depicts a typical day time or night time of street activity. Program objects include: seating, bike-share, pop-up art, community center, plaza, library, mobile grocery, grocery store, and basketball court.

Changing the Environment

Behavior — When a program is deployed in the environment, agents will be attracted to the program within a certain range.

Parameters — Each program is weighted with a multiplier that establishes the strength of attraction for each demographic. Sliders on the sidebar will allow users to further customize attraction levels for specific program.

Inputs — Inputs include the population size for each agent, time of day of the environment, the location, number, and type of program deployed, and the weighted parameters associated with each program for each agent.

Outputs — The primary outputs of the model return a live simulation and display of the count of each demographic population on screen.

Learnings — We want to survey user interaction by documenting what programs are deployed, where programs are deployed, and how it reflects stakeholders’ programmatic priorities. If behavior could be codified to address social complexities, it could also be useful to understand if a certain combination of program yields higher pedestrian activity and social integration.

playLab.Hudson code

import math
import random
from behaviors import *
from drag_program import *
#active map area
mapX = 1300
mapY = 750
class Fish(object):
type = ('high', 'low', 'tourist')
fishcolors = (color(145, 204, 130), color(1, 153, 154), color(243, 123, 128))
def __init__(self):

self.position = [random.randrange(extent[0],extent[2]), random.randrange(extent[1],extent[3])]
self.speed = random.random()*2 +1
self.direction = random.random() * 2.0 * math.pi - math.pi
self.turnrate = 0

#assign identity
chance = random.random()
if chance < high:
identity = 0
elif chance < low+high:
identity = 1
else:
identity = 2

self.fishcolor = Fish.fishcolors[identity]
self.type = Fish.type[identity]
self.id = identity
#print self.id
def move(self):
# TODO Globals... Yuck.
global allfishes, behaviors
state = {}
state2 = {}
state3 = {}

for fish in allfishes:
for behavior in behaviors:
if fish.id == 0:
behavior.setup(self, fish, high_attraction, state)
elif fish.id == 1:
behavior.setup(self, fish, low_attraction, state2)
else:
behavior.setup(self, fish, tourist_attraction, state3)

for behavior in behaviors:
if self.id == 0:
s = state
elif self.id == 1:
s = state2
else:
s = state3
strokeWeight(1)
behavior.apply(self, s)
stroke(self.fishcolor)
#behavior.draw(self, s)

def draw(self):
pushMatrix()
translate(*self.position)
rotate(self.direction)

#draw people here
stroke(self.fishcolor)
fill(self.fishcolor)
ellipse(0,0,5,5)
noFill()
arc(0,0,20,20, 0.75*PI, PI/2+0.75*PI)
fill(150)
textSize(20)
#text(self.type, -10, -20)
popMatrix()#create drag program classes
program1 = DragProgram(40,770)
program2 = DragProgram(180,770)
program3 = DragProgram(320,770)
program4 = DragProgram(460,770)
program5 = DragProgram(600,770)
program6 = DragProgram(740,770)
program7 = DragProgram(880,770)
program8 = DragProgram(1020,770)
program9 = DragProgram(1160,770)
#button functions [text label, image file, background color, population color]
left = ["", "left.png"]
slider = ["", "slider.png"]
right = ["", "right.png"]
trash = ["TRASH", "box.png"]
reset = ["RESET", "reset.png"]
#button class
class Button(object):
def __init__(self, bSize, bX, bY):
self.bSize = bSize
self.bX = bX
self.bY = bY

def display(self, function):
global mouseOver
self.function = function
self.bText = function[0]
self.bImg = loadImage(function[1])

if (mouseX > self.bX
and mouseX < self.bX + self.bSize
and mouseY > self.bY
and mouseY < self.bY + self.bSize):
mouseOver = True
else:
mouseOver = False
if mouseOver == True:
tint(200)
image(self.bImg, self.bX, self.bY, self.bSize, self.bSize)
fill(0)
textAlign(CENTER)
textSize(12)
text(self.bText, self.bX+self.bSize/2, self.bY+self.bSize+15)
noTint()
#create buttons
buttonLeft1 = Button(50, 1320, 450)
buttonRight1 = Button(50, 1530, 450)
buttonSlider1 = Button(40, 1430, 455)
buttonLeft2 = Button(50, 1320, 550)
buttonRight2 = Button(50, 1530, 550)
buttonSlider2 = Button(40, 1430, 555)
buttonLeft3 = Button(50, 1320, 650)
buttonRight3 = Button(50, 1530, 650)
buttonSlider3 = Button(40, 1430, 655)
buttonTime = Button(50, 1425, 350)
buttonTrash = Button(100, 1325, 770)
buttonReset = Button(100, 1475, 770)
def setup():
size(1600, 900)
smooth()

#simulation extent
global extent
extent = [ 0, 0, mapX, mapY]

# How many fish you want in the simulation.
number_of_people = 50
# Add all the fish behaviors and their parameters.
global behaviors
behaviors = (
#MoveTowardsCenterOfNearbyFish(closeness= 50.0 threshold=25.0, speedfactor=100.0, weight=20.0),
MoveTowardsAttraction(threshold=200.0, speedfactor=1.0, weight=60.0),
#TurnAwayFromClosestFish(threshold=15.0, speedfactor=5.0, weight=20.0),
#TurnToAverageDirection(closeness=50.0, weight=6.0),
Swim(speedlimit=10.0, turnratelimit=math.pi / 20.0)
#WrapAroundWindowEdges()
)
#current population
global population
population = 0

global high, low, tourist
high = 0.4
low = 0.3
tourist = 0.4

# Make some fish!
global allfishes
allfishes = []
for i in xrange(0, number_of_people):
allfishes.append(Fish())
population += 1


global attraction, high_attraction, low_attraction, tourist_attraction
high_attraction = [program2, program5, program6]
low_attraction = [program1, program2, program4, program7, program8, program9]
tourist_attraction = [program1, program2, program3]

global imgPlayLab, toggleTime, textDay, timeDay, timeNight, dayBlocks, dayBldgs, nightBldgs, nightBlocks

#button functions [text label, image file, background color, population color]
dayBlocks = loadImage("day_blocks.png")
dayBldgs = loadImage("day_bldgs.png")
nightBlocks = loadImage("night_blocks.png")
nightBldgs = loadImage("night_bldgs.png")
timeDay = ["DAY", "day.png", 255, 0, dayBlocks, dayBldgs]
timeNight = ["NIGHT", "night.png", 50, 255, nightBlocks, nightBldgs]
toggleTime = timeDay

#sidebar
imgPlayLab = loadImage("playLab.Hudson.png")

#population
population = [["Resident, High Income", 145, 204, 130], #high
["Resident, Low Income", 1, 153, 154], #low
["Tourist", 243, 123, 128] #tourist
]
#setup drag program
program1.setup()
program2.setup()
program3.setup()
program4.setup()
program5.setup()
program6.setup()
program7.setup()
program8.setup()
program9.setup()
def draw():

noStroke()
background(toggleTime[2])
image(toggleTime[4], 0, 0, mapX, mapY)
stroke(toggleTime[3])
strokeWeight(1)
if mouseX < mapX and mouseY < mapY:
line(0, mouseY, mapX, mouseY)
line(mouseX, 0, mouseX, mapY)

#navbar
fill(255)
rect(0, mapY, mapX, 150)
stroke(0)
strokeWeight(4)
line(0, mapY, mapX, mapY)
noStroke()
#sidebar
fill(255)
rect(mapX, 0, 300, height)
stroke(0)
strokeWeight(4)
line(mapX, 0, mapX, height)

#sidebar objects
image(imgPlayLab, 1350, 10, 200, 67)
strokeWeight(3)
line(1355, 475, 1545, 475)
line(1355, 575, 1545, 575)
line(1355, 675, 1545, 675)

#display buttons
buttonLeft1.display(left)
buttonRight1.display(right)
buttonSlider1.display(slider)
buttonLeft2.display(left)
buttonRight2.display(right)
buttonSlider2.display(slider)
buttonLeft3.display(left)
buttonRight3.display(right)
buttonSlider3.display(slider)

buttonTime.display(toggleTime)
buttonTrash.display(trash)
buttonReset.display(reset)


high_count = 0
low_count = 0
tourist_count = 0

#regenerate fishes
for fish in allfishes:
fish.move()
fish.draw()

if fish.position[0] > extent[2]:
allfishes.append(Fish())
allfishes.pop(allfishes.index(fish))

elif fish.position[0] < 0:
allfishes.append(Fish())
allfishes.pop(allfishes.index(fish))

elif fish.position[1] > extent[3]:
allfishes.append(Fish())
allfishes.pop(allfishes.index(fish))

elif fish.position[1] < 0:
allfishes.append(Fish())
allfishes.pop(allfishes.index(fish))

if fish.id == 0:
high_count += 1
population[0].append(high_count)
elif fish.id == 1:
low_count += 1
population[1].append(low_count)
else:
tourist_count += 1
population[2].append(tourist_count)
#buildings img
image(toggleTime[5], 0, 0, mapX, mapY)

#display drag program
program1.display(40, seating)
program2.display(180, bikeShare)
program3.display(320, art)
program4.display(460, communityCenter)
program5.display(600, plaza)
program6.display(740, library)
program7.display(880, mobileGrocery)
program8.display(1020, groceryStore)
program9.display(1160, basketball)

#sidebar people counter
for p in range (0,3):
textAlign(LEFT)
textSize(12)
stroke(0)
strokeWeight(1)
noFill()
rect(1375, 200+p*50, 50, 20)
fill(0)
text(population[p][0], 1435, 215+p*50)
text(population[p][-1], 1380, 215+p*50)

pushMatrix()
translate(1360, 210+p*50)
scale(.5)
stroke(population[p][1], population[p][2], population[p][3])
fill(population[p][1], population[p][2], population[p][3])
ellipse(0,0,15,15)
noFill()
arc(0,0,50,50,0.75*PI, (PI/2+0.75*PI))
popMatrix()


#click button toggle
def mouseClicked():
global toggleTime
if (buttonTime.bX < mouseX < buttonTime.bX+buttonTime.bSize
and buttonTime.bY < mouseY < buttonTime.bY+buttonTime.bSize
and toggleTime == timeDay):
toggleTime = timeNight
elif (buttonTime.bX < mouseX < buttonTime.bX+buttonTime.bSize
and buttonTime.bY < mouseY < buttonTime.bY+buttonTime.bSize
and toggleTime == timeNight):
toggleTime = timeDay
elif (buttonLeft1.bX < mouseX < buttonLeft1.bX+buttonLeft1.bSize
and buttonLeft1.bY < mouseY < buttonLeft1.bY+buttonLeft1.bSize
and buttonSlider1.bX > 1355):
buttonSlider1.bX -= 20
elif (buttonRight1.bX < mouseX < buttonRight1.bX+buttonRight1.bSize
and buttonRight1.bY < mouseY < buttonRight1.bY+buttonRight1.bSize
and buttonSlider1.bX+buttonSlider1.bSize < 1545):
buttonSlider1.bX += 20
elif (buttonLeft2.bX < mouseX < buttonLeft2.bX+buttonLeft2.bSize
and buttonLeft2.bY < mouseY < buttonLeft2.bY+buttonLeft2.bSize
and buttonSlider2.bX > 1355):
buttonSlider2.bX -= 20
elif (buttonRight2.bX < mouseX < buttonRight2.bX+buttonRight2.bSize
and buttonRight2.bY < mouseY < buttonRight2.bY+buttonRight2.bSize
and buttonSlider2.bX+buttonSlider2.bSize < 1545):
buttonSlider2.bX += 20
elif (buttonLeft3.bX < mouseX < buttonLeft3.bX+buttonLeft3.bSize
and buttonLeft3.bY < mouseY < buttonLeft3.bY+buttonLeft3.bSize
and buttonSlider3.bX > 1355):
buttonSlider3.bX -= 20
elif (buttonRight3.bX < mouseX < buttonRight3.bX+buttonRight3.bSize
and buttonRight3.bY < mouseY < buttonRight3.bY+buttonRight3.bSize
and buttonSlider3.bX+buttonSlider3.bSize < 1545):
buttonSlider3.bX += 20

behaviors class code (modified version of Allen William Martin’s Fish Schooling agent-based model)

import mathclass Behavior(object):
def __init__(self, **parameters):
self.parameters = parameters
def setup(self, fish, otherfish, attraction, state):
pass
def apply(self, fish, state):
pass
def draw(self, fish, state):
pass
# move towards attractions
class MoveTowardsAttraction(Behavior):
def setup(self, fish, otherfish, attraction, state):
if 'closest_attraction' not in state:
state['closest_attraction'] = None
if 'distance_to_closest_attraction' not in state:
state['distance_to_closest_attraction'] = 1000000
for program in attraction:
#print attraction
distance_to_program = dist(
program.position[0], program.position[1],
fish.position[0], fish.position[1]
)

if distance_to_program < state['distance_to_closest_attraction']:
state['distance_to_closest_attraction'] = distance_to_program
state['closest_attraction'] = program

def apply(self, fish, state):
closest_attraction = state['closest_attraction']
if closest_attraction is None:
return
distance_to_closest_attraction = state['distance_to_closest_attraction']
if distance_to_closest_attraction < self.parameters['threshold'] and closest_attraction.position[1] < height-150:
angle_to_closest_attraction = math.atan2(
fish.position[1] - closest_attraction.position[1],
fish.position[0] - closest_attraction.position[0]
)
fish.turnrate += (angle_to_closest_attraction - fish.direction) / self.parameters['weight']
fish.speed += self.parameters['speedfactor'] / distance_to_closest_attraction
def draw(self, fish, state):
noFill()
closest = state['closest_attraction']
distance_to_closest_attraction = state['distance_to_closest_attraction']
if closest.position[1] < height-150:
if distance_to_closest_attraction < self.parameters['threshold']:
line(fish.position[0], fish.position[1], closest.position[0], closest.position[1])
stroke(200,200,100)
ellipse(closest.position[0], closest.position[1], self.parameters['threshold'] * 2, self.parameters['threshold'] *2)
class Swim(Behavior):
def setup(self, fish, otherfish, attraction, state):
#fish.speed =
fish.turnrate = 0
def apply(self, fish, state):
# Move forward, but not too fast.
if fish.speed > self.parameters['speedlimit']:
fish.speed = self.parameters['speedlimit']
fish.position[0] -= math.cos(fish.direction) * fish.speed
fish.position[1] -= math.sin(fish.direction) * fish.speed

drag program class code

#drag program [text label, image file]
seating = ["SEATING", "1_seating.png"]
bikeShare = ["BIKE SHARE", "2_bike-share.png"]
art = ["POP-UP ART", "3_popup-art.png"]
communityCenter = ["COMMUNITY CENTER", "4_community-center.png"]
plaza = ["PLAZA", "5_plaza.png"]
library = ["LIBRARY", "6_library.png"]
mobileGrocery = ["MOBILE GROCERY", "7_mobile-grocery.png"]
groceryStore = ["GROCERY STORE", "8_grocery-store.png"]
basketball = ["BASKETBALL COURT", "9_basketball.png"]
#drag program class
class DragProgram(object): # Class definition
def __init__(self, xpos, ypos): # Object constructor
self.xpos = xpos
self.ypos = ypos
self.box_size = 100
global overBox, lock, xOffset, yOffset, drag
overBox = False
lock = False
drag = False
xOffset = 0
yOffset = 0

def setup(self):
self.position = [(self.xpos+self.box_size/2),(self.ypos+self.box_size/2)]

def display(self, xText, program): # Display method
global overBox, lock, drag, xOffset, yOffset

self.program = program
self.xText = xText
self.programText = program[0]
self.programImg = loadImage(program[1])

if (mouseX > self.xpos
and mouseX < self.xpos + self.box_size
and mouseY > self.ypos
and mouseY < self.ypos + self.box_size):
overBox = True
tint(200)
else:
overBox = False
noTint()
if mousePressed == True and overBox == True:
xOffset = mouseX - self.xpos
yOffset = mouseY - self.ypos

tint(100)
self.xpos = mouseX - (self.box_size/2)
self.ypos = mouseY - (self.box_size/2)

image(self.programImg, self.xpos, self.ypos, self.box_size, self.box_size)
fill(0)
textAlign(CENTER)
textSize(12)
text(self.programText, self.xText+self.box_size/2, 880)
noTint()

self.position = [(self.xpos+self.box_size/2),(self.ypos+self.box_size/2)]

Future Implications — This project has the potential to inform programmatic decisions made for locals and visitors in the Hudson Valley. Based on programs appealing to residents and visitors, the city of Hudson can use its resources to provide new amenities that would benefit both locals and tourist. The outcome of the simulation would set up a new ground to then build around and inform where certain bus stops, restaurants, and other complementing programs are located. In an ideal world, this simulation would allow residents and visitors to design their own city. The user would be able to enter a city, through augmented reality, that would be an outcome of their programmatic aspirations.

--

--

Ashley Louie
Data Mining the City

With a background in architecture and urban design, I work with data and technology for social good.