Api.video Analytics: Add Markers Representing Viewer Density Per City to a Map with Observable

April 11, 2021 - Erikka Innes in Analytics, Python, JavaScript

If you want to advertise on a budget, it's easier when you know where your content is already succeeding. With api.video's analytics, you can see what cities and countries viewers are visiting your site from. This kind of information can give you a general sense of where you might want to spend (or not spend) your advertising dollars.

You could view your data in a list, but it might be faster if you represented the data visually with something like a map that shows viewer density per city. Today, we're going to use OpenLayers, Observable, and api.video's data analytics for a demo account to retrieve what we need to plot all the cities viewers watch live streams from on a map. We'll represent how many sessions (viewers) there are per city with a circle that has a bigger circumference if there's more viewers, and a smaller one if there's less viewers. We won't fill in the circles so we can see markers for all overlapping cities.

Let's start mapping!

A map showing viewer density per city using api.video data, Observable, Geocoding, and Open Layers

A map showing viewer density per city using api.video data, Observable, Geocoding, and Open Layers


For this tutorial, you'll need:

  • api.video account - Sign up for api.video here.
  • Observable account
  • OpenCage Geocoding API account
  • requests, csv, pycountry, opencage, and pandas libraries for Python

If you don't know JavaScript, or the version of it that Observable's using, you don't have to. This tutorial will explain how to configure your map in a way where you don't have to know the JavaScript. If there's interest in having the JavaScript content explained more, let us know in our community forum! We can explain everything in a future post.

Build Your Data File

In this section, we'll create the data file we want to use with our map. The code sample will:

  • Authenticate with the api.video API.
  • Retrieve a list of all our live streams.
  • Retrieve all sessions for each live stream and for each session, create a dictionary containing the live stream title, the session ID for the session, the city, the country, and we'll also add the latitude and longitude coordinates using the OpenCage Geocoding API.
  • Write each dictionary to a line in a csv file that we can then use in a pandas dataframe.

Here's the code:

import requests
import csv
import pycountry
from opencage.geocoder import OpenCageGeocode

# Get api.video token 

url = "https://ws.api.video/auth/api-key"

payload = {"apiKey": "your api key here"}
headers = {
    "Accept": "application/json",
    "Content-Type": "application/json"

response = requests.request("POST", url, json=payload, headers=headers)
response = response.json()
token = response.get("access_token")

# Set up Geocoder Authentication 
geocoder = OpenCageGeocode('your opencage key here')

# Takes a response from the first query and determines if it's paginated
# Returns data in a list you can iterate over for information for sessions
# or videos and live streams. 

def paginated_response(url, token):
    headers = {
    "Accept": "application/json",
    "Authorization": token
    json_response = requests.request("GET", url, headers=headers, params={})
    json_response = json_response.json()
    total_pages = 1
    if json_response is not None:
        total_pages = json_response['pagination']['pagesTotal']
    video_info = list(json_response['data'])
    if total_pages > 1:
        for i in range(2, total_pages +1):
            querystring = {"currentPage":str(i), "pageSize":"25"}
            r = requests.request("GET", url, headers=headers, params=querystring)
            r = r.json()
            video_info = video_info + r['data']
    return video_info

livestreams = paginated_response("https://ws.api.video/live-streams", token)

with open('master_sessions.csv', 'w', newline='') as csv_file:
    fieldnames = ['Title', 'SessionID', 'Country', 'City', 'Latitude', 'Longitude']
    writer = csv.DictWriter(csv_file, fieldnames)

    for item in livestreams:
        live_url = 'https://ws.api.video/analytics/live-streams/' + item['liveStreamId']
        title = item['name']
        ID = item['liveStreamId']
        print('when are we arriving here')
        live_sessions = paginated_response(live_url, token)
        for item in live_sessions:
            if item['location']['city'] is None:
                item['location']['city'] = 'unknown' 
            if item['location']['country'] is None:
                item['location']['country'] = 'unknown'
            address = item['location']['city'] + ', ' + item['location']['country']
            coords = geocoder.geocode(address)
            lat = coords[0]['geometry']['lat']
            lng = coords[0]['geometry']['lng']
            if item['location']['country'] == 'unknown':
                iso3 = '---'
            if item['location']['country'] != 'unknown':
                iso3 = pycountry.countries.get(name=item['location']['country']) 
                if iso3 != None: 
                    iso3 = iso3.alpha_3
                    iso3 = '---'
            writer.writerow({'Title':title, 'SessionID':item['session']['sessionId'], 'Country':item['location']['country'], 'City':item['location']['city'], 'Latitude':lat, 'Longitude':lng})

In the code we choose to use 'open with' to open the csv file we'll write to, because this method will close the file for us when it's done writing everything. We also use the dictionary writing feature of the csv module for Python. This allows us to create a header row listing all the columns we want. We can then create a dictionary where every key matches a column title. Whatever we set as the value will be written to the csv file as a row.

We add latitude and longitude for every city in our file, and then we output to the file ls_session_data.csv. The next step is to add our CSV file to a pandas dataframe.

Create a Dataframe With Your CSV File

We convert our brand new CSV file to pandas so we can manipulate the data. There are other ways you can do this, but once you have the hang of pandas, you can do a lot with it.

Here's the code for moving our csv file to pandas, grouping everything and then outputting it back to a csv file we call 'country_lat_lon.csv:

import pandas as pd 

df = pd.read_csv("ls_session_data.csv")

df_grouped = df.groupby(['Country']).agg(Count=pd.NamedAgg(column="City", aggfunc="count"))


Now we're ready to build our map.

Configure Observable

In this section, we walk through how to get your map set up on Observable. If you haven't done so, sign up for an Observable account. Then do the following:

  1. Navigate to our Observable notebook Where api.video Live Streams Are Watched.
  2. Click the three dots in the upper right corner. A menu opens.
  3. From the menu, choose Fork. Now you have your own copy of the map to edit and play around with.
  4. Click the three dots in the upper right corner again and choose File attachments.
  5. Delete the existing copy of country_lat_lon.csv and upload your copy. Don't try to upload expecting an overwrite - it will add a 1 at the end of your file. Then you will have to tweak the file name in the code. (Where you do that is explained in the Observable notebook if you decide to go that route instead.)
  6. Make sure your data is loaded. You can check if anything needs an update by looking for a solid blue triangle in the upper right corner of the cells on this page. Click any you see.
  7. Your map should display, showing viewer density by city through the size of the circle used as a marker.

Tweaking Observable and OpenLayers

You can tweak this map lots of different ways, or go back to the one it's forked from and tweak that instead. Some suggestions for areas to try changing things:

  1. Go to the Set the Center section. The array listed shows a pair of coordinates that are longitude, then latitude. If you change these, you can change the center the map starts out with.
  2. Go to the Define vectorSource, Programmatically Add Markers section. In the block of code that starts data.forEach you can change how big or small the circles are by changing what you multiply Count by. This creates the radius of your circle.
  3. Go to the Add Your Data section. If you click the three dots to the left of data = Array(45) you'll be able to see the import code. If you want you can use a different file name, or change the code to upload a different kind of file for use in the project (though that might have more effects on the rest of the code).
  4. You can see different types of markers you can add by looking at another user's post about using OpenLayers - Fork of Openlayers to put some markers/features on.

If you try a map, be sure to share it with us in our community forum! We'd love to see what you do with your data.

Erikka Innes

Developer Evangelist

Get started now

Connect your users with videos