Here's the formulation of the linear program and the Gurobi code to solve it:

**Decision Variables:**

* `x`: Number of taxi ads
* `y`: Number of bus ads
* `z`: Number of privately owned car ads

**Objective Function:**

Maximize total viewership: `5000x + 12000y + 2000z`

**Constraints:**

* **Budget Constraint:** `500x + 1000y + 300z <= 20000`
* **Bus Ad Limit:** `y <= 8`
* **Taxi Ad Proportion:** `x <= 0.3 * (x + y + z)`
* **Private Car Ad Proportion:** `z >= 0.2 * (x + y + z)`
* **Non-negativity:** `x, y, z >= 0`
* **Integer Constraint:** `x, y, z` must be integers


```python
import gurobipy as gp
from gurobipy import GRB

# Create a new model
m = gp.Model("Ad Optimization")

# Create variables
x = m.addVar(vtype=GRB.INTEGER, name="taxi_ads")
y = m.addVar(vtype=GRB.INTEGER, name="bus_ads")
z = m.addVar(vtype=GRB.INTEGER, name="car_ads")

# Set objective function
m.setObjective(5000*x + 12000*y + 2000*z, GRB.MAXIMIZE)

# Add constraints
m.addConstr(500*x + 1000*y + 300*z <= 20000, "budget")
m.addConstr(y <= 8, "bus_limit")
m.addConstr(x <= 0.3 * (x + y + z), "taxi_proportion")
m.addConstr(z >= 0.2 * (x + y + z), "car_proportion")

# Optimize model
m.optimize()

# Print results
if m.status == GRB.OPTIMAL:
    print(f"Optimal Viewership: {m.objVal}")
    print(f"Taxi Ads: {x.x}")
    print(f"Bus Ads: {y.x}")
    print(f"Car Ads: {z.x}")
else:
    print("Infeasible or unbounded")

```
