The REST API version 6.1
Content#
As our project includes a REST API we want to explain how to use it. So far we implemented three methods to use it. One is running a local web server and using a browser, the others are to use CSharp under Windows or Swift under MacOS to get the data from the server. This leads to the three topics discussed in this post.
If you haven’t installed Python on your machine you may do it now. As well you might learn about finalizing the setup of the development system after installing Python. This are the topics we will discuss here:
- Prerequisites to setup the system
- Using the REST API in a browser
- Using CSharp to access the REST API
- Using Swift to access the REST API
Prerequisites#
For the sake of an easy integration in a web server the REST API 6.1 requires a environment variable called COVID_DATA to be set on the system. This variable has to point to a directory in which the REST API is looking for the following files. The files should be generated once per day and on mb-cmbt.de they are generated between 11:00 and 12:00 CET:
Filename | Description |
---|---|
MapAfrica.html | A map showing the data for African countries |
MapAmerica.html | A map showing the data for American countries |
MapAsia.html | A map showing the data for Asian countries |
MapEurope.html | A map showing the data for European countries |
MapOceania.html | A map showing the data for Oceania countries |
MapWorld.html | A map showing the data for all countries of the world |
MapDEageAndGenderCounty.html | A map showing the German data for different age and gender groups down to city/county level |
MapDEageAndGenderState.html | A map showing the German data for different age and gender groups in state level |
MapDEcounty.html | A map showing the German data down to city/county level |
MapDEState.html | A map showing the German data in state level |
WHO-data-processed.csv | A CSV with all data of all countries since 01.01.2020, this is simply a copy of the cache file of the WHO data |
All of the maps can be generated using the CovidFoliumMapGenerator.py script. The filename of the cache file is returned by the method get_cache_filename of the CovidCases class. To create a environment variable on your local or target system follow the instructions below.
MacOS Catalania or higher support the ZSH (Z shell) and the Bash shell, while earlier versions of MacOS support the Bash shell only. That’s why we have to distinguish between Bash and ZSH to modify the environment.
Modifying the Bash environment under MacOS#
For Bash you need to open the file .bash_profile. The file contains additional settings of your bash and it will be loaded every time you start a new bash. Open Finder and move to the User folder. To view hidden files press ⇧ + ⌘ + . (shift, command and period symbol “."):
If you don’t see the file called .bash_profile we have to create it by executing the following command from the terminal:
touch .bash_profile
Now the file will show up in *Finder_*and you can open it in an editor of your choice and ensure that it includes the following line:
export COVID_DATA="<your_path>"
Modifying the ZSH environment under MacOS#
For ZSH you need to open the file .zshrc The file contains additional settings of your Z shell and it will be loaded every time you start a new shell. Open Finder and move to the User folder. To view hidden files press ⇧ + ⌘ + . (shift, command and period symbol “."):
If you don’t see the file called .zshrc we have to create it by executing the following command from the terminal:
touch .zshrc
Now the file will show up in *Finder_*and you can open it in an editor of your choice and ensure that it includes the following line:
export COVID_DATA="<your_path>"
Modifying the environment under Linux (here Ubuntu)#
There are different methods to create environment variables under Linux. We will use a global persistent setting stored under /etc/profile. So we create a new file under /etc/profile.d to store the global environment variable in a file. Use the following command to create such a file which we will call covid-data.sh and than edit the file using nano, vim or any other editor:
sudo touch /etc/profile.d/covid-data.sh
sudo nano /etc/profile.d/covid-data.sh
Add the following line to the file:
export COVID_DATA="<your_path>"
You may want to refer to the users home directory which can be done as shown here:
export COVID_DATA=$HOME/<your_path>
To apply the settings we have to close the current terminal session and restart it. As we want to test the REST API now we should generate the maps first, otherwise the REST API will return an error string. To do so we start the CovidFoliumMapGenerator application. The application will take some time but finally you will notice 10 generated maps in the COVID_DATA directory.
Using the REST API in a browser#
Now we want to make use of the REST API to access the data using the browser. This will allow us to integrate graphs and choropleth maps in a web site. Expand the rest folder and open the file named app.py:
To execute the file use terminal and switch to the Covid-19-Analysis/src/rest
directory and start uvicorn using the following command:
uvicorn app:app
Start your browser and connect to http://localhost:8000/api/data/DE,UK/Cases? to show a graph of daily cases in Germany and the UK. To stop uvicorn switch back to your terminal and press CTRL + C
.
The browser will connect to your local server and of course you can connect to it from a separate machine using the IP address of your local system. You may also use our own web server: try this link mb.cmbt.de/api/data/DE,UK/Cases?. If you connect to mb.cmbt.de/docs you will find a simplified user interface to test the REST API.
The options of the address to get data plots are as follow:
http://localhost:8000/api/data/GeoIDlist/Field?ParameterOptions'
Part | Description |
---|---|
GeoIDlist | the comma separated list of GeoIDs of countries you want to get the data for. E.g. DE,UK,FR |
Field | The data field that you want to display: |
Cases: The overall number of confirmed infections (here called cases) since December 31st. 2019 as pubished by the ECDC | |
DailyCases: The daily number of confirmed cases | |
DailyCases7: The average number of DailyCases of the last 7 days | |
Deaths: The overall number of deaths of confirmed cases | |
DailyDeaths: The daily number of deaths of confirmed cases | |
DailyDeaths7: The 7-day average of the daily number of deaths | |
PercentDeaths: The percentage of deaths of the confirmed cases. This is also called Case-Fatality-Rate (CFR) which is an estimation for the Infection-Fatality-Rate (IFR) which also includes unconfirmed (hidden or dark) infections | |
DoublingTime: The time in days after which the number of Cases are doubled | |
DoublingTime7: The time in days after which the number of Cases are doubled as a 7 day average | |
CasesPerMillionPopulation: The number of Cases divided by the population in million | |
DeathsPerMillionPopulation: The number of Deaths divided by the population in million | |
R: An estimation for the reproduction number R | |
R7: An estimation for the reproduction number R in a 7-day average | |
Incidence7DayPer100Kpopulation: The accumulated 7-day incidence. That is the sum of the daily cases of the last 7 days divided by the population in 100000 people | |
PeopleReceivedFirstDose: Number of citizens that received first vaccination dose | |
PercentPeopleReceivedFirstDose: Percent of citizens that received first vaccination dose | |
PeopleReceivedAllDoses: Number of citizens that received all vaccination doses | |
PercentPeopleReceivedAllDoses: Percent of citizens that received all vaccination doses | |
VaccineDosesAdministered: The overall number of vaccination doses that have been administered | |
DailyVaccineDosesAdministered7DayAverage: The daily number of vaccination doses that have been administered in a seven day average | |
Parameter | Either no parameter or one of the following: |
sinceN=X | The number of cases since the X number of cases was exceeded. E.g. sinceN=100 |
lastN=X | To show the last X number of days. E.g. lastN=30 |
dataSource=X | The data source to be used. Can be WHO or OWID.. E.g. dataSource=WHO |
Options | None or one or a combination of the following: |
log=True | To show a logarithmic y axis |
axisbar=True | To show a bar graph |
As a result the REST API will return a PNG file showing the plot of the selected data of the selected countries using the given options.
Now lets open http://localhost:8000/api/maps/MapEurope to see the map of European countries:
The options of the address to get the maps are as follow:
http://localhost:8000/api/map/Parameter
Parameter | Description |
---|---|
MapAfrica | A map showing the data for African countries |
MapAmerica | A map showing the data for American countries |
MapAsia | A map showing the data for Asian countries |
MapEurope | A map showing the data for European countries |
MapOceania | A map showing the data for Oceania countries |
MapWorld | A map showing the data for all countries of the world |
MapDEageAndGenderCounty | A map showing the German data for different age and gender groups down to city/county level |
MapDEageAndGenderState | A map showing the German data for different age and gender groups in state level |
MapDEcounty | A map showing the German data down to city/county level |
MapDEState | A map showing the German data in state level |
As a result the REST API will return a HTML file of the selected region. The HTML file includes some interaction that show up when you move your mouse over the map.
Last but not least you can download the plain data of all countries since 1.1.2020 by opening http://localhost:8000/api/csv. This function has no additional parameters:
Here are some more examples you can try and that are executed on the mb.cmbt.de server:
- http://mb.cmbt.de/api/data/DE%2CUK/Incidence7DayPer100Kpopulation?lastN=30&bar=true
- http://mb.cmbt.de/api/data/DE%2CFR%2CIT/Cases?sinceN=500&log=true&bar=false
Using CSharp to access the REST API#
Using REST APIs is simple. Try to open this link to the Google QR code web service
It will show a QR code containing the string Hello World. You can find more information about the Google APIs here. The API just returns a PNG showing the QR code. It’s the same as the response to our api/data function.
The repository of Covid-19 Analysis on GitHub includes a subdirectory called CSWebClient. Here you will find a full sample that you can build and execute. Open the sample in Visual Studio and hit F5 to start it. Select the countries you want to see and click Get data. The sample will show the address that it is using in the status line, connect to the server that is still running under Python and displays the result:
Most of the job of the sample is to create the URL that is used to get the data from. The function that returns a Bitmap from the URL is pretty simple:
private System.Drawing.Bitmap GetChartFromURL(string strURL)
{
// the result
System.Drawing.Bitmap result = null;
// create a web client
System.Net.WebClient wc = new System.Net.WebClient();
// download image data
Byte[] image = wc.DownloadData(strURL);
// put it in a stream
System.IO.MemoryStream stream = new System.IO.MemoryStream(image);
// create image from stream
result = new System.Drawing.Bitmap(stream);
return result;
}
It’s up to you what you do with this image, but using it is simple.
Using Swift to access the REST API#
Like in C# it’s simple to use a REST API under Swift. The Covid-19 Anaysis repository on GitHub includes a subdirectory called Covid19WebClient. Here you will find a full sample that you can build and execute. Open the sample in XCode and hit ⌘ R to start it. Select the countries you want to see or select one of the favorites and click Retrieve Plot to get the data. The sample will show the address that it is using in the status line, connect to the mb.cmbt.de website that is running the REST API under Python and display the result:
As in C# most of the job of the sample is to create the URL that is used to get the data from. The function that returns a NSImage from the URL is pretty simple:
private func GetChartFromURL(strURL: String) -> Result<NSImage, NetworkError> {
// the url
guard let url = URL(string: strURL) else {
// there is a problem with the url
return Result.failure(NetworkError.INVALID_URL)
}
// get the image from the url
do {
if let result = try NSImage(data: Data(contentsOf: url)) {
// great, it worked, return the image
return Result.success(result)
}
// obviously the server did not returned an imahe
return Result.failure(NetworkError.INVALID_RESPONSE)
}
catch {
// we got no answer at all
return Result.failure(NetworkError.NO_RESPONSE)
}
}
You will notice that the function actually returns one of two different result types, a nice feature of Swift that is covered by the Swift type Result
. The Result
type has two cases: success
and failure
. Both are generics so you can define what their values are. For success
we return a NSImage, but failure
must confirm to Swift’s error
protocol and that’s an enum. We use NetworkError
and it’s defined by this:
enum NetworkError:Error {
/// Something is wrong with the URL, potentially it's because of the App Transport Security Settings (no http connections allowed) or the sandboxing
case INVALID_URL
/// The server is not responding
case NO_RESPONSE
/// The server does not respond data of the expected type (e.g. no image)
case INVALID_RESPONSE
}
Having the return type in place we can call GetChartFromURL
e.g. like in this function:
func GetDataChartLast(strCountries: String, strAttribut: String, bLogarithmic: Bool, bBargraph: Bool, nLast: Int32) -> (image: Result<NSImage, NetworkError>, strURL: String)
{
// build the url string
var strURL: String = "http";
if (_UseHTTPS) {
strURL = strURL + "s"
}
// remove spaces from the GeoID string list
let strCountriesNoSpaces = strCountries.replacingOccurrences(of: " ", with: "")
// add these GeoID list to the url
strURL = strURL + "://" + _server + _urlPath + strCountriesNoSpaces + "/" + strAttribut + "?dataSource=" + DataSource.rawValue + "&lastN=" + String(nLast)
// add the log flag
if (bLogarithmic) {
strURL = strURL + "&log=True"
}
// add the bargraph flag
if (bBargraph) {
strURL = strURL + "&bar=True"
}
// finally get the image and the url that has been used from the server
return (GetChartFromURL(strURL: strURL), strURL)
}
This function seems to be even more strange as it returns a tuple of values of which one is the result of GetChartFromURL
(a NSImage
or the NetworkError
type). The second value of the tuple is the URL that has been used to get the data.
Using this becomes obvious when we look at the function that is finally using the result and that is in the ViewController
:
...
// now get the data
var theResult: (image: Result<NSImage, CoronaWebClient.NetworkError>, strURL: String)
switch _dateOption {
case 0:
// get the data
theResult = _wc.GetDataChart(strCountries: theGeoIDs, strAttribut: theAttribute, bLogarithmic: logarithmic, bBargraph: barGraph)
case 1:
// get the data
theResult = _wc.GetDataChartLast(strCountries: theGeoIDs, strAttribut: theAttribute, bLogarithmic: logarithmic, bBargraph: barGraph, nLast: stpLastNDays.intValue)
case 2:
// get the data
theResult = _wc.GetDataChartSince(strCountries: theGeoIDs, strAttribut: theAttribute, bLogarithmic: logarithmic, bBargraph: barGraph, nSince: stpSinceNCases.intValue)
default:
return
}
...
It basically defines theResult
as the return type of the function and than calls it. Checking theResult
is than done like in this snippet:
...
// check the return
switch theResult {
// success
case let (Result.success(image), url):
imageView.image = image
txtStatus.stringValue = "Connected to " + _strServer + ". Request URL is: " + url
// enable the menu items to save and copy the plot
enableMenuItems()
// server not found at all
case let (Result.failure(CoronaWebClient.NetworkError.INVALID_URL), url):
txtStatus.stringValue = "Error connecting to " + _strServer + ". Request URL is: " + url
// server returned no image
case let (Result.failure(CoronaWebClient.NetworkError.INVALID_RESPONSE), url):
txtStatus.stringValue = "Error in server reply. Request URL is: " + url + ". Maybe server doesn't send an image."
// server doesn't respond
case let (Result.failure(CoronaWebClient.NetworkError.NO_RESPONSE), url):
txtStatus.stringValue = "Error server didn't respond. Request URL is: " + url + ". Maybe an illegal GeoID?"
}
...
Both language, C# and Swift, are using pretty modern approaches to force you to write less defective code and give you good instruments to do so.