Towards A Sponge City

Sep 24, 2019 · 7 min read

By Rui Wang & Shuo Han

An interactive model of the floods formation and simulates how precipitation, drainage system, green ratio would collectively influence flooding in the city.

Image for post
Image for post
Simulation of How Green Lands in the City relieve Flooding


Under the circumstance of climate change, the weather gets more extreme and brings about disastrous consequences. On the other hand, the green lands in the city are being exploited for other uses, which in the end would deteriorate the condition. The model shows how the green lands can relieve the flooding to a certain degree and argues for proposals like Sponge City and City Farming to increase the city’s resilience to climate change.

Principle & Visulization:

The precipitation and green lands ratio can be controlled. If the precipitation excess the total amount of the drainage system, there will be dynamic flooding alert on the road plane. The water level is counted as m/20 days.

-Phase 1

Image for post
Image for post
Municiple Drainage System Works

Municipal drainage system works to drain away water if the precipitation is small. And you wouldn’t see flooding alert.

-Phase 2

Image for post
Image for post
Flooding Alerts when Precipitation Excesses Drainage Capacity
Image for post
Image for post
Green Lands work to Drain Water Away

The city begins to be flooding because of the large quantity of precipitation and the orginal municiple drainage is not enough. But by changing the green ratio, green lands could help drain away water to avoid flooding.

-Phase 3

Image for post
Image for post
Flooding Can’t Be Avoided

When the green ratio comes to its largest capacity, the flooding couldn’t be avoided. But its still helpful to have green lands to help relieve.


Greenland ratio (the component ‘plane’) and rainfall (the component ‘cube’)


In this interactive model we try to simulate the effect of urban green-land ratio on the phenomenon of flooding in urban areas. Green lands, as one kind of penetrable land cover, helps rainwater converge to the groundwater, preventing the city from flooding. As the ratio grows, the potential risk of flooding would decrease. Otherwise, it would increase.


Given that this is based on realistic statistics although it’s an abstract model, we adopted the widely-accepted formula and parameters for the two main value for our model, which is the rainfall and the overall drainage volume. Demonstration is as follows:

l Rainfall: The widely-accept way to measure daily precipitation is to use a measuring cylinder whose radius is 2 centimeters, the height of the rainwater in the cylinder after a whole day would be the daily precipitation, and the dimension would be millimeter. Define this data as DP (daily precipitation), and define the area of the measuring cylinder as A. If we want to figure out the volume of rainwater, and use the dimension of m³/s, the expression would be


1.2*10^(-7)*A*DP …… …… …… ……E1

l Overall drainage volume:

This contains two parts, which refer to the part of green lands and the part of city drainage system.

The volume of rainwater penetrated to groundwater by green lands (V) per second would be:


α- Comprehensive safety factor, 0.5–0.6

K- Soil permeability coefficient (m/s), we use an average value of 9.0*(10^-6) here

J- Hydraulic gradient, 1

A’- Effective penetration area (㎡), which is greenland ratio (GR) multiply A

t-time (s)

The expression would be:


4.5*10^(-6)*A*GR …… …… …… ……E2

l The volume of rainwater emission by the city drainage system (V’) per second would be:

(0.00074*A)/200 …… …… …… ……E3

If the rainfall per second is greater than the overall drainage volume per second, there wold be risk of flooding in the city. In our model, if E1>E2+E3, a flooding alert would be triggered, and the color of the ground would turn red.

Given that DP and GR are controlled by the sliders, the minimum of them are 0, and the maximum are 1, so we remapped the parameters linearly. The final expression to justify if there’s risk of flooding is like this:



The inputs refer to the 2 variables in the model, which is the green-land ratio and the rainfall, controlled by the two sliders.


The outputs refer to the color of the land (component ‘plane’), which would turn red when there’s risk of flooding and remain gray when there’s no risk, and the flooding water (component ‘cube’), whose height would change according to the change of green-land ratio and rainfall.

Water Material

Shader "Unlit/NewUnlitShader"
Properties {
_Color ("Main Color", Color) = (0, 0.15, 0.115, 1)
_MainTex ("Base (RGB)", 2D) = "white" {}
_WaveMap ("Wave Map", 2D) = "bump" {}
_Cubemap ("Environment Cubemap", Cube) = "_Skybox" {}
_WaveXSpeed ("Wave Horizontal Speed", Range(-0.1, 0.1)) = 0.01
_WaveYSpeed ("Wave Vertical Speed", Range(-0.1, 0.1)) = 0.01
_Distortion ("Distortion", Range(0, 100)) = 10
SubShader {
Tags { "Queue"="Transparent" "RenderType"="Opaque" }
GrabPass { "_RefractionTex" }
Pass {
Tags { "LightMode"="ForwardBase" }


#include "UnityCG.cginc"
#include "Lighting.cginc"

#pragma multi_compile_fwdbase

#pragma vertex vert
#pragma fragment frag

fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _WaveMap;
float4 _WaveMap_ST;
samplerCUBE _Cubemap;
fixed _WaveXSpeed;
fixed _WaveYSpeed;
float _Distortion;
sampler2D _RefractionTex;
float4 _RefractionTex_TexelSize;

struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
struct v2f {
float4 pos : SV_POSITION;
float4 scrPos : TEXCOORD0;
float4 uv : TEXCOORD1;
float4 TtoW0 : TEXCOORD2;
float4 TtoW1 : TEXCOORD3;
float4 TtoW2 : TEXCOORD4;

v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.scrPos = ComputeGrabScreenPos(o.pos);
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex); = TRANSFORM_TEX(v.texcoord, _WaveMap);
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(;
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;

o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);

return o;

fixed4 frag(v2f i) : SV_Target {
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
float2 speed = _Time.y * float2(_WaveXSpeed, _WaveYSpeed);
fixed3 bump1 = UnpackNormal(tex2D(_WaveMap, + speed)).rgb;
fixed3 bump2 = UnpackNormal(tex2D(_WaveMap, - speed)).rgb;
fixed3 bump = normalize(bump1 + bump2);
float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy;
fixed3 refrCol = tex2D( _RefractionTex, i.scrPos.xy/i.scrPos.w).rgb;
bump = normalize(half3(dot(, bump), dot(, bump), dot(, bump)));
fixed4 texColor = tex2D(_MainTex, i.uv.xy + speed);
fixed3 reflDir = reflect(-viewDir, bump);
fixed3 reflCol = texCUBE(_Cubemap, reflDir).rgb * texColor.rgb * _Color.rgb;

fixed fresnel = pow(1 - saturate(dot(viewDir, bump)), 4);
fixed3 finalColor = reflCol * fresnel + refrCol * (1 - fresnel);

return fixed4(finalColor, 1);

FallBack Off

Water Level

public class RainQuantity : MonoBehaviour
public Slider RQ;
public Slider GR;

public GameObject SQ;

public GameObject WL;

public GameObject FL;
public GameObject WH;
public Text SQT;
public Text WLT;
public Text FLT;
public Text ShowSpeed;
public Text ShowPercent;
float z1;
float z2;
// Start is called before the first frame update
void Start()


// Update is called once per frame
void Update()
z1 = (float) (1.0368 * RQ.value)*30;
var value = GR.value;
z2 = (float) (0.07 * value + 0.32)*30;
SQ.transform.localScale = new Vector3(10f, (float) (z1*0.5), 10f);
SQ.transform.position=new Vector3(-100f, (float) (0.25*z1), -120f);
WL.transform.localScale = new Vector3(10f, (float) (0.5*z2), 10f);
WL.transform.position=new Vector3(-75f, (float) (0.25*z2), -120f);
FL.transform.localScale = new Vector3(10f, (float) (0.5*(z1-z2)), 10f);
FL.transform.position=new Vector3(-50f, (float) (0.25*(z1-z2)), -120f);
WH.transform.localScale = new Vector3(200f, (float) (0.5*(z1-z2)), 200f);
WH.transform.position=new Vector3(0, (float) (0.25*(z1-z2)), 0);
SQT.transform.localPosition=new Vector3(100,-515+z1*2,0);
WLT.transform.localPosition=new Vector3(200,-570+z2*2,0);
FLT.transform.localPosition=new Vector3(300,-620+(z1-z2)*2,0);
SQT.text = (int) (100*z1)*0.01 + "m/20 D";
WLT.text = (int) (100*z2)*0.01 + "m/20 D";
FLT.text = (int) (100*(z1 - z2))*0.01 + "m/20 D";
ShowSpeed.text = (int) (100 * z1 / 30) * 0.01 + "m/24 h";
ShowPercent.text = (int) (100 * value) + "%";

Flooding Alert

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using UnityEngine.UI;

public class Flooding : MonoBehaviour
public Slider GR;
public Slider RQ;

private Material myMaterial;

// Start is called before the first frame update
float x;
float y;
float z1;
float z2;

void Start()
myMaterial = GetComponent<MeshRenderer>().material;


// Update is called once per frame
void Update()
y = GR.value;
x = RQ.value;
z1 = (float) (1.2 * Math.Pow(10, -5) * x);
z2 = (float) (8.1 * Math.Pow(10, -7) * y + 3.7 * Math.Pow(10, -6));
if (z1> z2)
myMaterial.color = new Color((z1-z2)*900000, 0,0);
else if(z1<=z2)
myMaterial.color = new Color(0,0,0);

Green Lands

public class ScaleControl : MonoBehaviour
public Slider Greenratio;
public GameObject Green1;
public GameObject Green2;
public GameObject Green3;
public GameObject Green4;
public GameObject Green5;
public GameObject Green6;
public GameObject Green7;
public GameObject Green8;
// Start is called before the first frame update
void Start()


// Update is called once per frame
void Update()
Green1.transform.localScale = new Vector3(Greenratio.value, 1.0f, Greenratio.value);
Green2.transform.localScale = new Vector3(Greenratio.value, 1.0f, Greenratio.value);
Green3.transform.localScale = new Vector3(Greenratio.value, 1.0f, Greenratio.value);
Green4.transform.localScale = new Vector3(Greenratio.value, 1.0f, Greenratio.value);
Green5.transform.localScale = new Vector3(Greenratio.value, 1.0f, Greenratio.value);
Green6.transform.localScale = new Vector3(Greenratio.value, 1.0f, Greenratio.value);
Green7.transform.localScale = new Vector3(Greenratio.value, 1.0f, Greenratio.value);
Green8.transform.localScale = new Vector3(Greenratio.value, 1.0f, Greenratio.value);


Data Mining the City

Graduate course at Columbia University GSAPP, taught by…

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store