Evacuation Simulation in Urban Fabrics

Yicheng Xu
Data Mining the City
11 min readDec 11, 2017

Columbia University, Datamining the city, Final presentation

Group: Chenyu Xu, Huanyu Chen, Ruilan Jia, Shuting Han, Yicheng Xu
(Names in alphabetical order)

1.Background

When facing unpredictable disasters such as hurricane, earthquake, fire, car accident and terror attack, human can be very vulnerable. For terror attack alone, there are 1076 attacks and 7396 fatalities worldwide in 2017 so far. In densely populated cities, there is a higher chance of accidents in hotspots resulting in relevantly more injuries and fatalities.

Lately, on May 18, 2017, a car was crashed in Times Square, New York City. One person was killed and 20 were injured.

On December 31, 2014, a deadly stampede occurred in Shanghai, near Chen Yi Square on the Bund, where around 300,000 people had gathered for the New Year celebration. 36 people were killed and there were 49 injured, 13 seriously.

A deadly stampede in Shanghai

Cities should contain complex refuge, acting as shelter from emergency. The roles of urban fabric and physical infrastructure are crucial not only in preventing terrorists’ actions but also determining the way of evacuating people. Even though security might be the most effective way to prevent accidents directly, city design should be improved to evacuate people as fast as possible to reduce the impact.

Among all the different types of disasters mentioned above, we assumed one extreme case when the damage source(Attacker) is mobile meaning that it could have huge potential impact, and visualized it in realistic urban areas.

This project uses an agent-based model to simulate human behaviors during an emergency in global cities, aiming to evaluate efficiency of evacuation in different types of urban fabric. The efficiency is assessed by quantifying fatalities over the same time.

2.Methodology

2.1 Define objects

Define Objects
def setup():
size(1000,1000)
frameRate(30)

# How many people you want in the simulation.
number_of_victim =200
number_of_threat = 1
global baseImage, allexitsonImage
baseImage = loadImage("Victorian_Harbour.jpg")
allexitsonImage = [{"location":(960,740),"count1":0,"count2":0},{"location":(40,790),"count1":0,"count2":0}]


# Add all the victim and threat behaviors and their parameters.
global victimSelfbehaviors,victimRespondbehaviors,threatbehaviors, victimbehaviors

victimbehaviors = Run1(speedlimit=2.0, turnratelimit=math.pi / 20)

victimSelfbehaviors = (
MoveTowardsExit(allexits=allexitsonImage,closeness_exit=300,weight=10),
TurnAwayFromWall_1(weight=10.0, BaseImage = baseImage),
Escape(EscapeRadius=60),
MoveTowardsCenterOfNearbyVictims1(closeness=50.0, threshold=25.0, speedfactor=100.0, weight=40.0),
TurnAwayFromClosestVictim(threshold=15.0, speedfactor=5.0, weight=20.0),
TurnToAverageDirection(closeness=50.0, weight=40.0),
WrapAroundWindowEdges1()
)

victimRespondbehaviors = (
Die(KillingRadius=20),
TurnAwayFromThreat(threshold=100, speedfactor=200, weight=2),
)

threatbehaviors = (
MoveTowardsCenterOfNearbyVictims2(closeness=200.0, threshold=0, speedfactor=50.0, weight=10.0),
TurnAwayFromWall_2(weight=10.0, BaseImage = baseImage),
Run2(speedlimit=8.0, turnratelimit=math.pi / 10.0),
WrapAroundWindowEdges2()
)
# Make some people!
global allvictims, allthreats #allpeople
allvictims = []
allthreats = []
for i in xrange(0, number_of_victim):
allvictims.append(Victim(baseImage))

for j in xrange(0, number_of_threat):
allthreats.append(Threat(baseImage))
def draw():
background(255)
image(baseImage,0,0)
for victim in allvictims:
victim.move()
victim.draw()
for threat in allthreats:
threat.move()
threat.draw()
for i in allexitsonImage:
#noFill()
fill(100,255,100)
#stroke(0,255,255)
noStroke()
rect(i['location'][0],i['location'][1],10,10)

filename="VH-{0}.jpg".format(frameCount)
print filename

saveFrame(filename)

if frameCount == 1200:
Dead=0
Escaped=0
Stillalive=0
for victim in allvictims:
if victim.life==0:
Dead+=1
elif victim.life==1:
Stillalive+=1
elif victim.life==2:
Escaped+=1
print Dead
print Escaped
print Stillalive
exit()
class Victim(object):
def __init__(self, baseImage):
# Need the baseImage to position the victim on a white pixel
x = random.randrange(400, 600)
y = random.randrange(400, 600)
br = brightness(baseImage.get(x, y))
while br < 20:
x = random.randrange(0, width)
y = random.randrange(0, height)
br = brightness(baseImage.get(x, y))
self.position = [x, y]
self.speed = 0.01
self.direction = random.random() * 2.0 * math.pi - math.pi
self.turnrate = 0
self.victimcolor=[color(0,0,0),color(255,255,0)]
self.life = 1
self.exitsaround = []
self.exit_to_go=()
def move(self):

if self.life == 0:
return
if self.life == 2:
return

global allvictims, victimSelfbehaviors,victimRespondbehaviors, allthreats, victimbehaviors
state = {}# here defining the otherfishes, the "self" in behavior functions doesn't need to be states again when begin a function, so "self" here is read as "fish" in the behavior functions, "fish" here is read as "otherfish" in behavior functions

# This nested loop structure is really expensive. Don't do it
# if it's not strictly necessary for the behaviors of this agent.
for othervictim in allvictims:
if othervictim.life == 1:
for behavior in victimSelfbehaviors:
behavior.setup(self, othervictim, state)
victimbehaviors.setup(self, othervictim, state)
for behavior in victimSelfbehaviors:
behavior.apply(self, state)
behavior.draw(self, state)

for threat in allthreats:
for behavior in victimRespondbehaviors:
behavior.setup(self, threat, state)
for behavior in victimRespondbehaviors:
behavior.apply(self, state)
#behavior.draw(self, state)

victimbehaviors.apply(self, state)
#victimbehaviors.draw(self,state)


def draw(self):
if self.life == 0:
pushMatrix()

stroke(0,0,0)
translate(*self.position)
rotate(self.direction)
fill(self.victimcolor[1])
triangle(-10,0,4,-4,4,4)


popMatrix()
if self.life == 1:
pushMatrix()

noStroke()
translate(*self.position)
rotate(self.direction)
fill(self.victimcolor[0])
triangle(-10,0,4,-4,4,4)

popMatrix()
if self.life == 2:
pass
'''pushMatrix()
translate(*self.position)
rotate(self.direction)
fill(self.victimcolor[1])
triangle(-10,0,4,-4,4,4)
popMatrix()'''
class Threat(object):
def __init__(self, baseImage):
#self.position = [random.randrange(0, width), random.randrange(0, height)]
x = random.randrange(400, 600)
y = random.randrange(500, 700)
br = brightness(baseImage.get(x, y))
while br < 20:
x = random.randrange(450, 550)
y = random.randrange(450, 550)
br = brightness(baseImage.get(x, y))
self.position = [x,y]
self.speed = 0.02
self.direction = random.random() * 2.0 * math.pi - math.pi
self.turnrate = 0
self.threatcolor=color(255,0,0)
self.life=1
def move(self):

global allthreats, threatbehaviors, allvictims
state = {}
live=1
live2=2
# here defining the otherfishes, the "self" in behavior functions doesn't need to be states again when begin a function, so "self" here is read as "fish" in the behavior functions, "fish" here is read as "otherfish" in behavior functions
for victim in allvictims:
if victim.life==1:
#print victim
for behavior3 in threatbehaviors:
behavior3.setup(self, victim, state)

for behavior in threatbehaviors:
behavior.apply(self, state)
behavior.draw(self, state)
def draw(self):
pushMatrix()
translate(*self.position)
rotate(self.direction)
fill(self.threatcolor)
triangle(-20,0,8,-8,8,8)

popMatrix()

2.2 Define Behavior

People Behavior
People interact with Attacker

2.2 People Behaviors

2.2.1 Move Towards Center Of Nearby People

class MoveTowardsCenterOfNearbyVictims1(VictimSelfBehavior):
def setup(self, victim, othervictim, state):
if victim is othervictim:
return
if 'closecount' not in state:
state['closecount'] = 0.0
if 'center' not in state:
state['center'] = [0.0, 0.0]
closeness = self.parameters['closeness']
distance_to_othervictim = dist(
othervictim.position[0], othervictim.position[1],
victim.position[0], victim.position[1]
)
if distance_to_othervictim < closeness:
if state['closecount'] == 0:
state['center'] = othervictim.position
state['closecount'] += 1.0
else:
state['center'][0] *= state['closecount']
state['center'][1] *= state['closecount']
state['center'] = [
state['center'][0] + othervictim.position[0],
state['center'][1] + othervictim.position[1]
]
state['closecount'] += 1.0state['center'][0] /= state['closecount']
state['center'][1] /= state['closecount']
def apply(self, victim, state):
if state['closecount'] == 0:
return
center = state['center']
distance_to_center = dist(
center[0], center[1],
victim.position[0], victim.position[1]
)
if distance_to_center > self.parameters['threshold']:
angle_to_center = math.atan2(
victim.position[1] - center[1],
victim.position[0] - center[0]
)
if state['closecount'] <= 20:
victim.turnrate += (angle_to_center - victim.direction) / self.parameters['weight']
victim.speed += distance_to_center / self.parameters['speedfactor']
if state['closecount'] > 20:
victim.turnrate -= (angle_to_center - victim.direction) / self.parameters['weight']
victim.speed += (self.parameters['closeness'] - distance_to_center) / self.parameters['speedfactor']
def draw(self, victim, state):
closeness = self.parameters['closeness']
stroke(200, 200, 255)
noFill()

2.2.2 Turn Away From Closest Person

class TurnAwayFromClosestVictim(VictimSelfBehavior):
def setup(self, victim, othervictim, state):
if victim is othervictim:
return
if 'closest_victim' not in state:
state['closest_victim'] = None
if 'distance_to_closest_victim' not in state:
state['distance_to_closest_victim'] = 1000000
distance_to_othervictim = dist(
othervictim.position[0], othervictim.position[1],
victim.position[0], victim.position[1]
)
if distance_to_othervictim < state['distance_to_closest_victim']:
state['distance_to_closest_victim'] = distance_to_othervictim
state['closest_victim'] = othervictim
def apply(self, victim, state):
closest_victim = state['closest_victim']
if closest_victim is None:
return
distance_to_closest_victim = state['distance_to_closest_victim']
if distance_to_closest_victim < self.parameters['threshold']:
angle_to_closest_victim = math.atan2(
victim.position[1] - closest_victim.position[1],
victim.position[0] - closest_victim.position[0]
)
victim.turnrate -= (angle_to_closest_victim - victim.direction) / self.parameters['weight']
victim.speed += self.parameters['speedfactor'] / distance_to_closest_victim
def draw(self, victim, state):
stroke(100, 255, 100)
closest = state['closest_victim']
#line(victim.position[0], victim.position[1], closest.position[0], closest.position[1])

2.2.3 Turn To Average Direction

class TurnToAverageDirection(VictimSelfBehavior):
def setup(self, victim, othervictim, state):
if victim is othervictim:
return
if 'average_direction' not in state:
state['average_direction'] = 0.0
if 'closecount_for_avg' not in state:
state['closecount_for_avg'] = 0.0
distance_to_othervictim = dist(
othervictim.position[0], othervictim.position[1],
victim.position[0], victim.position[1]
)
closeness = self.parameters['closeness']
if distance_to_othervictim < closeness:
if state['closecount_for_avg'] == 0:
state['average_direction'] = othervictim.direction
state['closecount_for_avg'] += 1.0
else:
state['average_direction'] *= state['closecount_for_avg']
state['average_direction'] += othervictim.direction
state['closecount_for_avg'] += 1.0
state['average_direction'] /= state['closecount_for_avg']
def apply(self, victim, state):
if state['closecount_for_avg'] == 0:
return
average_direction = state['average_direction']
victim.turnrate += (average_direction - victim.direction) / self.parameters['weight']

2.2.4 Move Towards Exit

class MoveTowardsExit(VictimSelfBehavior):
def setup(self, victim, othervictim, state):
if victim.life == 1:
closeness = self.parameters['closeness_exit']
for i in xrange(len(self.parameters['allexits'])):
distance_to_exit = dist(self.parameters['allexits'][i]['location'][0],self.parameters['allexits'][i]['location'][1],victim.position[0],victim.position[1])
if distance_to_exit < closeness:
self.parameters['allexits'][i]['count2'] += 1
victim.exitsaround.append(self.parameters['allexits'][i])
#print victim.exitsaround

if victim.exitsaround == []:
pass
else:
minivictim=1000
for exitaround in victim.exitsaround:
if exitaround['count1'] < minivictim:
minivictim=exitaround['count1']
victim.exit_to_go = exitaround['location']

def apply(self, victim, state):

for i in xrange(len(self.parameters['allexits'])):
self.parameters['allexits'][i]['count1']=self.parameters['allexits'][i]['count2']
self.parameters['allexits'][i]['count2']=0

if victim.exitsaround == []:
return
else:
distance_to_exit = dist(victim.position[0],victim.position[1],victim.exit_to_go[0],victim.exit_to_go[0])
angle_to_exit = math.atan2(victim.position[1]-victim.exit_to_go[1],victim.position[0]-victim.exit_to_go[0])
victim.turnrate += (angle_to_exit-victim.direction)/self.parameters['weight']
victim.speed += distance_to_exit / self.parameters['weight']

victim.exitsaround=[]
#victim.exit_to_go=()
def draw(self, victim, state):
#print victim.exit_to_go
if victim.exit_to_go == ():
pass
else:
#line (victim.position[0],victim.position[1],victim.exit_to_go[0],victim.exit_to_go[1])
pass
victim.exit_to_go=()
class Escape(VictimSelfBehavior):
def setup(self, victim, othervictim, state):
Escaperadius = self.parameters['EscapeRadius']
if 'life' not in state:
state['life'] = 1
for allexit in victim.exitsaround:
distance_to_exit = dist(allexit['location'][0],allexit['location'][1],victim.position[0],victim.position[1])
if distance_to_exit <=60:
victim.life = 2
state['life'] = 2
def apply(self, victim, state):
if state['life'] == 2:
victim.life = 2
def draw(self,victim, state):
pass

2.2.5 Turn Away From Wall

def setup(self, victim, othervictim, state):
img=self.parameters['BaseImage']

# define location of antenna
if 'antenna' not in state:
state['antenna']=[1,1,1]

x = victim.position[0]
y = victim.position[1]

direction = victim.direction
direction1 = direction + math.pi / 12.0
direction2 = direction - math.pi / 12.0
distance = -10 # Because of a motion bug in the original example,
# the agents are actually moving backwards.
# Compensate by using a negative antenna length.

#antenna1
x0=floor(x+distance*cos(direction))
if x0 >= width:
x0 = x0-width
if x0 < 0:
x0 = width+x0
y0=floor(y+distance*sin(direction))
if y0 >= height:
y0 = y0-height
if y0 < 0:
y0 = height+y0

#antenna2
x1=floor(x+distance*cos(direction1))
if x1 >= width:
x1 = x1-width
if x1 < 0:
x1 = width+x1
y1=floor(y+distance*sin(direction1))
if y1 >= height:
y1 = y1-height
if y1 < 0:
y1 = height+y1

#antenna3
x2=floor(x+distance*cos(direction2))
if x2 >= width:
x2 = x2-width
if x2 < 0:
x2 = width+x2
y2=floor(y+distance*sin(direction2))
if y2 >= height:
y2 = y2-height
if y2 < 0:
y2 = height+y2

xs=[x0,x1,x2]
ys=[y0,y1,y2]
# define if victims collide with walls
#img = loadImage("Times_Square.jpg")
#img.loadPixels()
for i in xrange(3):
# Draw the antenna for debugging
stroke(128, 0, 0)
point(xs[i], ys[i])

# extract the pixel color underneath the antenna
px = img.get(xs[i], ys[i])
b = brightness(px)

if b < 20:
state['antenna'][i] = 0
else:
state['antenna'][i] = 1

# turn direction
def apply(self, victim, state):
if 'antenna' not in state:
self.setup(victim, None, state)

if state['antenna'][0] == 0:
if state['antenna'][1] == 0 and state['antenna'][2] == 1:
victim.turnrate -= (math.pi/2) #/ self. parameters['weight']
elif state['antenna'][1] == 1 and state['antenna'][2] == 0:
victim.turnrate += (math.pi/2) #/ self. parameters['weight']
else:
'''t=random.randrange(0,2)
if t <=1:
factor=1
else:
factor=-1'''
victim.turnrate += (math.pi/2) #/ self. parameters['weight']

2.2.6 Turn Away From Attacker

class TurnAwayFromThreat(VictimRespondBehavior):
def setup(self, victim, threat, state):
if 'closest_threat' not in state:
state['closest_threat'] = None
if 'distance_to_closest_threat' not in state:
state['distance_to_closest_threat'] = 1000000
distance_to_threat = dist(
victim.position[0], victim.position[1],
threat.position[0], threat.position[1]
)
if distance_to_threat < state['distance_to_closest_threat']:
state['distance_to_closest_threat'] = distance_to_threat
state['closest_threat'] = threat
def apply(self, victim, state):
closest_threat = state['closest_threat']
if closest_threat is None:
return
distance_to_closest_threat = state['distance_to_closest_threat']
if distance_to_closest_threat < self.parameters['threshold']:
angle_to_closest_threat = math.atan2(
victim.position[1]-closest_threat.position[1] ,
victim.position[0]-closest_threat.position[0]
)
victim.turnrate -= (angle_to_closest_threat - victim.direction) / self.parameters['weight']
victim.speed += self.parameters['speedfactor'] / distance_to_closest_threat
def draw(self, victim, state):
stroke(100, 255, 100)
closest = state['closest_threat']
line(victim.position[0], victim.position[1], closest.position[0], closest.position[1])

2.2.7 People Get Injured By Attacker

class Die(VictimRespondBehavior):
def setup(self, victim, threat, state):
killingradius = self.parameters['KillingRadius']
if 'life' not in state:
state['life'] = 1
distance_to_threat=dist(threat.position[0],threat.position[1],victim.position[0],victim.position[1])
if distance_to_threat <= killingradius:
state['life'] = 0
def apply(self, victim, state):
if state['life'] == 0:
victim.life = 0
def draw(self,victim, state):
pass

2.2.8 Speed Up (SelfBehavior)

class Run1(VictimSelfBehavior):
def setup(self, victim, othervictim, state):
victim.speed = 1
victim.turnrate = 0
def apply(self, victim, state):

if victim.turnrate > self.parameters['turnratelimit']:
victim.turnrate = self.parameters['turnratelimit']
if victim.turnrate < -self.parameters['turnratelimit']:
victim.turnrate = -self.parameters['turnratelimit']
victim.direction += victim.turnrate

if victim.speed > self.parameters['speedlimit']:
victim.speed = self.parameters['speedlimit']
victim.position[0] -= math.cos(victim.direction) * victim.speed
victim.position[1] -= math.sin(victim.direction) * victim.speed
if victim.direction > math.pi:
victim.direction -= 2 * math.pi
if victim.direction < -math.pi:
victim.direction += 2 * math.pi

2.2.9 Speed Up (Interaction)

class Run3(VictimRespondBehavior):
def setup(self, victim, threat, state):
#if victim.speed is None:
victim.speed = 0
#if victim.turnrate is None:
victim.turnrate = 0
def apply(self, victim, state):

if victim.turnrate > self.parameters['turnratelimit']:
victim.turnrate = self.parameters['turnratelimit']
if victim.turnrate < -self.parameters['turnratelimit']:
victim.turnrate = -self.parameters['turnratelimit']
victim.direction += victim.turnrate

if victim.speed > self.parameters['speedlimit']:
victim.speed = self.parameters['speedlimit']
victim.position[0] -= math.cos(victim.direction) * victim.speed
victim.position[1] -= math.sin(victim.direction) * victim.speed
if victim.direction > math.pi:
victim.direction -= 2 * math.pi
if victim.direction < -math.pi:
victim.direction += 2 * math.pi

2.3 Attacker Behaviors

2.3.1 Move Towards Center Of Nearby People

class MoveTowardsCenterOfNearbyVictims2(ThreatBehavior):
def setup(self, threat, victim, state):
if 'closecount' not in state:
state['closecount'] = 0.0
if 'center' not in state:
state['center'] = [0.0, 0.0]
closeness = self.parameters['closeness']
distance_to_victim = dist(
victim.position[0], victim.position[1],
threat.position[0], threat.position[1]
)
if distance_to_victim < closeness:
if state['closecount'] == 0:
state['center'] = victim.position
state['closecount'] += 1.0
else:
state['center'][0] *= state['closecount']
state['center'][1] *= state['closecount']
state['center'] = [
state['center'][0] + victim.position[0],
state['center'][1] + victim.position[1]
]
state['closecount'] += 1.0state['center'][0] /= state['closecount']
state['center'][1] /= state['closecount']
def apply(self, threat, state):
if state['closecount'] == 0:
return
center = state['center']
distance_to_center = dist(
center[0], center[1],
threat.position[0], threat.position[1]
)
if distance_to_center > self.parameters['threshold']:
angle_to_center = math.atan2(
threat.position[1] - center[1],
threat.position[0] - center[0]
)
threat.turnrate += (angle_to_center - threat.direction) / self.parameters['weight']
threat.speed += distance_to_center / self.parameters['speedfactor']
def draw(self, threat, state):
closeness = self.parameters['closeness']
stroke(200, 200, 255)
noFill()
ellipse(threat.position[0], threat.position[1], closeness * 2, closeness * 2)
#ellipse(otherfish.position[0], otherfish.position[1], closeness * 0.1, closeness * 0.1)
#print otherfish.position'''

2.3.2 Speed Up

class Run2(ThreatBehavior):
def setup(self, threat, victim, state):
threat.speed = 2
threat.turnrate = 0
def apply(self, threat, state):
if threat.speed > self.parameters['speedlimit']:
threat.speed = self.parameters['speedlimit']
threat.position[0] -= math.cos(threat.direction) * threat.speed
threat.position[1] -= math.sin(threat.direction) * threat.speed
if threat.turnrate > self.parameters['turnratelimit']:
threat.turnrate = self.parameters['turnratelimit']
if threat.turnrate < -self.parameters['turnratelimit']:
threat.turnrate = -self.parameters['turnratelimit']
threat.direction += threat.turnrate
if threat.direction > math.pi:
threat.direction -= 2 * math.pi
if threat.direction < -math.pi:
threat.direction += 2 * math.pi

3.Site Context

We select 4 urban locations all highly populated in new year celebration from 4 typical urban gathering space.

I. Small-size street intersection square_Times Square, New York.

II. Large-size open square surrounded by urban roads_Trafalgar Square, London.

III. Large-size enclosed square surrounded by buildings_Piazza Del Campo, Siena.

IV. Urban linear open space_Victoria Harbour, Hong Kong.

4.Evacuation Stimulation in Processing

Overview of processing

https://vimeo.com/247191582

5. Analysis and Conclusion

5.1 Analysis

A. Regular experiment

B. When double the exit closeness distance

I. Small-size street intersection square_Times Square, New York.
II. Large-size open square surrounded by urban roads_Trafalgar Square, London.
III. Large-size enclosed square surrounded by buildings_Piazza Del Campo, Siena.
IV. Urban linear open space_Victoria Harbour, Hong Kong.

5.2 Result Analysis

People are more likely to die in the large-size open square.

More people escaped from small-size street intersection square and urban linear space.

People are more likely to be trapped in large-size enclosed square.

5.3 Conclusion

Create more signage to help people clearly know their locations and correct direction in large-size squares.

Enhance exit capacity and efficiency in enclosed squares.

--

--