## Problem Description and Symbolic Representation

The problem involves maximizing viewership given certain constraints on ad placements and budget. Let's denote:
- $x_1$ as the number of ads on taxis,
- $x_2$ as the number of ads on buses,
- $x_3$ as the number of ads on privately owned cars.

### Objective Function
The objective is to maximize viewership. Given:
- An ad on a taxi reaches 5000 viewers,
- An ad on a bus reaches 12000 viewers,
- An ad on a privately owned car reaches 2000 viewers,

the objective function can be represented as:
\[ \text{Maximize:} \quad 5000x_1 + 12000x_2 + 2000x_3 \]

### Constraints
1. **Budget Constraint**: The company has a budget of $20000.
   - An ad on a taxi costs $500,
   - An ad on a bus costs $1000,
   - An ad on a privately owned car costs $300,

   This translates to:
\[ 500x_1 + 1000x_2 + 300x_3 \leq 20000 \]

2. **Bus Company Limit**: The bus company limits the number of ads from a single company to at most 8.
\[ x_2 \leq 8 \]

3. **Taxi Ads Percentage**: At most 30% of the ads should be on taxis.
\[ x_1 \leq 0.3(x_1 + x_2 + x_3) \]
\[ 0.7x_1 - 0.3x_2 - 0.3x_3 \leq 0 \]

4. **Privately Owned Cars Ads Percentage**: At least 20% of ads should be on privately owned cars.
\[ x_3 \geq 0.2(x_1 + x_2 + x_3) \]
\[ -0.2x_1 - 0.2x_2 + 0.8x_3 \geq 0 \]

5. **Non-Negativity Constraint**: The number of ads cannot be negative.
\[ x_1 \geq 0, x_2 \geq 0, x_3 \geq 0 \]
And $x_1, x_2, x_3$ are integers.

## Symbolic Representation in JSON Format
```json
{
  'sym_variables': [('x1', 'taxis'), ('x2', 'buses'), ('x3', 'privately owned cars')],
  'objective_function': '5000*x1 + 12000*x2 + 2000*x3',
  'constraints': [
    '500*x1 + 1000*x2 + 300*x3 <= 20000',
    'x2 <= 8',
    '0.7*x1 - 0.3*x2 - 0.3*x3 <= 0',
    '-0.2*x1 - 0.2*x2 + 0.8*x3 >= 0',
    'x1 >= 0', 'x2 >= 0', 'x3 >= 0'
  ]
}
```

## Gurobi Code in Python
```python
import gurobipy as gp

def solve_ad_optimization():
    # Create a new model
    model = gp.Model("ad_optimization")

    # Define variables
    x1 = model.addVar(name="taxis", vtype=gp.GRB.INTEGER)  # ads on taxis
    x2 = model.addVar(name="buses", vtype=gp.GRB.INTEGER)  # ads on buses
    x3 = model.addVar(name="privately_owned_cars", vtype=gp.GRB.INTEGER)  # ads on privately owned cars

    # Objective function: Maximize viewership
    model.setObjective(5000*x1 + 12000*x2 + 2000*x3, gp.GRB.MAXIMIZE)

    # Budget constraint
    model.addConstr(500*x1 + 1000*x2 + 300*x3 <= 20000, name="budget_constraint")

    # Bus company limit
    model.addConstr(x2 <= 8, name="bus_limit")

    # Taxi ads percentage
    model.addConstr(0.7*x1 - 0.3*x2 - 0.3*x3 <= 0, name="taxi_percentage")

    # Privately owned cars ads percentage
    model.addConstr(-0.2*x1 - 0.2*x2 + 0.8*x3 >= 0, name="private_car_percentage")

    # Non-negativity constraints
    model.addConstr(x1 >= 0, name="x1_non_negative")
    model.addConstr(x2 >= 0, name="x2_non_negative")
    model.addConstr(x3 >= 0, name="x3_non_negative")

    # Solve the model
    model.optimize()

    if model.status == gp.GRB.Status.OPTIMAL:
        print("Optimal Solution:")
        print(f"Ads on taxis: {x1.varValue}")
        print(f"Ads on buses: {x2.varValue}")
        print(f"Ads on privately owned cars: {x3.varValue}")
        print(f"Max viewership: {model.objVal}")
    else:
        print("No optimal solution found.")

solve_ad_optimization()
```