
Contents
With its familiar syntax, it integrates smoothly with any Java program and can make use of several Java extensions.
In today’s guide we will write a complete piece of code in Groovy to generate an image with match statistics on any game of your choice.
Why Use Apache Groovy?
Although it sits in 25th place on popular programming languages according to a 2023 survey by Stackoverflow, Groovy is actively being used by several notable names, such as LinkedIn. Popular payment system Mastercard, music entertainment channel MTV, and software titans IBM, Oracle, and Google are all users of Apache Groovy, not to mention your favourite movie streaming platform, Netflix.
Thanks to a vibrant and rich ecosystem, a flat learning curve, and easy Java integration, among several other qualities, Apache Groovy remains an ideal choice for JSON manipulation.
Groovy comes with integrated support for converting between its objects and JSON, thanks to a package called groovy.json, which we shall use in this project.
Sportmonks & Apache Groovy.
Just like we did in the previous episode of our guide using Julia, at the end of this blog post, you’ll have an image showing match stats for any match of your choice.
In the upcoming sections, we’ll walk you through configuring your development environment before making your first call to the API.
As we drill down to stats for a specific fixture, each segment will build on the next, showcasing examples and code snippets.
Chapter 1: Setting up your environment
Before we get started, we will set up an environment to write and compile Groovy. After testing a handful of IDEs, we found JetBrains IntelliJ IDEA to be the easiest to work with. It also comes with a boatload of plugins and extensions in the marketplace.
1.1 Installing IntelliJ IDEA.
IntelliJ IDEA is a cross-platform IDE that provides a consistent experience on the Windows, macOS, and Linux operating systems.
You should have IntelliJ IDEA already installed on your workstation if you read our previous guide where we deployed Kotlin extensively.
If not, kindly download IntelliJ IDEA Community Edition, which is completely free to use.
We will set up our project in the next chapter after installing relevant plugins.
1.2 Obtaining an API Token
To use the API, sign up for an account on https://my.sportmonks.com. Your account automatically comes with the Danish Super Liga and Scottish Premier League plans, which are free forever.
However, for today’s exercise, you can take advantage of the 14-day free trial on any of the other plans if you haven’t done so previously.
Create an API token on the dashboard, which you must keep private. In the event that your API token becomes compromised, delete it and create a new one. An API token is required to authenticate our requests.
With your API token ready and your IDE installed, we can begin.
1.3 Create a Groovy Project.
We installed IntelliJ IDEA earlier on; however, to write and compile Groovy, we must install the required plugin and JDK version in our project.
As you can see in the image above, Groovy is not listed on the upper left side of IntelliJ. Search for Groovy via the ‘More via Plugins’ at the bottom left corner and install the plugin. If you encounter any problem, you may visit the official guide for help.
Once done, create a new project following the earlier steps.
Now you will be able to find Groovy on the left-hand side.
Enter the name of your project. However, an important step is selecting the proper version of Java. This is a common problem which could leave you tearing your hair out.
Groovy might be running on a version of Java which may be incompatible with the libraries or itself; hence, you must use a compatible JDK version. For Groovy 3.x, it requires Java 8-16, while for Groovy 4.x, it is compatible with Java 8-20. This may change in the future depending on the time you are reading this blog post, so always refer to the official help page shared earlier.
At this moment, since our version is Groovy 4.0.14, we will select JDK version 19. Once selected, proceed to have all libraries downloaded and navigate to your Main.groovy file, which comes with the legendary ‘Hello World’ example.
We will replace this sample with another piece of code in the next section.
Chapter 2: Retrieving Match Stats.
We will jump straight into retrieving match stats from a fixture of choice in this exercise.
However, if you would like to learn about how we adopted ‘filters’ and ‘includes’ to narrow down to specific stats of interest, you can read all about this in our previous blog with Julia.
2.2 Retrieving Stats using Includes.
Using an include statement with ‘statistics.type,’ we will retrieve all the match stats from the fixture ID 19134600, which was the game between Manchester City and Tottenham.
That fixtured ended in a 4-0 loss for the Sky Blues. It was Pep Guardiola’s record fifth successive defeat of his managerial career.
Using our URL of interest, we will write a piece of code in Groovy to retrieve the stats.
import groovy.json.JsonSlurper import groovy.json.JsonOutput import java.net.HttpURLConnection import java.net.URL def fetchData(apiUrl) { try { // Create a connection to the API URL def url = new URL(apiUrl) HttpURLConnection connection = (HttpURLConnection) url.openConnection() connection.setRequestMethod("GET") connection.setConnectTimeout(5000) connection.setReadTimeout(5000) // Read the response def response = connection.inputStream.text // Parse the JSON response def json = new JsonSlurper().parseText(response) // Pretty print the JSON def prettyJson = JsonOutput.prettyPrint(JsonOutput.toJson(json)) println("Data:\n${prettyJson}") return json } catch (Exception e) { println("Error: ${e.message}") } return null } // Provide the API URL def apiUrl = "https://api.sportmonks.com/v3/football/fixtures/19134600?api_token=API_Token&include=statistics.type" // Fetch and pretty print the data fetchData(apiUrl)
Data: { "data": { "id": 19134600, "sport_id": 1, "league_id": 8, "season_id": 23614, "stage_id": 77471288, "group_id": null, "aggregate_id": null, "round_id": 339246, "state_id": 5, "venue_id": 151, "name": "Manchester City vs Tottenham Hotspur", "starting_at": "2024-11-23 17:30:00", "result_info": "Tottenham Hotspur won after full-time.", "leg": "1/1", "details": null, "length": 90, "placeholder": false, "has_odds": true, "has_premium_odds": true, "starting_at_timestamp": 1732383000, "statistics": [ { "id": 154543270, "fixture_id": 19134600, "type_id": 56, "participant_id": 6, "data": { "value": 9 }, "location": "away", "type": { "id": 56, "name": "Fouls", "code": "fouls", "developer_name": "FOULS", "model_type": "statistic", "stat_group": "defensive" } }, { "id": 154461825, "fixture_id": 19134600, "type_id": 42, "participant_id": 9, "data": { "value": 23 }, "location": "home", "type": { "id": 42, "name": "Shots Total", "code": "shots-total", "developer_name": "SHOTS_TOTAL", "model_type": "statistic", "stat_group": "offensive" } }, { "id": 154543030, "fixture_id": 19134600, "type_id": 80, "participant_id": 9, "data": { "value": 566 }, "location": "home", "type": { "id": 80, "name": "Passes", "code": "passes", "developer_name": "PASSES", "model_type": "statistic", "stat_group": "overall" } }, { "id": 154541889, "fixture_id": 19134600, "type_id": 86, "participant_id": 6, "data": { "value": 7 }, "location": "away", "type": { "id": 86, "name": "Shots On Target", "code": "shots-on-target", "developer_name": "SHOTS_ON_TARGET", "model_type": "statistic", "stat_group": "offensive" } }, { "id": 154541880, "fixture_id": 19134600, "type_id": 86, "participant_id": 9, "data": { "value": 5 }, "location": "home", "type": { "id": 86, "name": "Shots On Target", "code": "shots-on-target", "developer_name": "SHOTS_ON_TARGET", "model_type": "statistic", "stat_group": "offensive" } }, { "id": 154543137, "fixture_id": 19134600, "type_id": 56, "participant_id": 9, "data": { "value": 19 }, "location": "home", "type": { "id": 56, "name": "Fouls", "code": "fouls", "developer_name": "FOULS", "model_type": "statistic", "stat_group": "defensive" } }, { "id": 154511651, "fixture_id": 19134600, "type_id": 45, "participant_id": 9, "data": { "value": 58 }, "location": "home", "type": { "id": 45, "name": "Ball Possession %", "code": "ball-possession", "developer_name": "BALL_POSSESSION", "model_type": "statistic", "stat_group": "overall" } }, { "id": 154511652, "fixture_id": 19134600, "type_id": 45, "participant_id": 6, "data": { "value": 42 }, "location": "away", "type": { "id": 45, "name": "Ball Possession %", "code": "ball-possession", "developer_name": "BALL_POSSESSION", "model_type": "statistic", "stat_group": "overall" } }, { "id": 154461826, "fixture_id": 19134600, "type_id": 42, "participant_id": 6, "data": { "value": 9 }, "location": "away", "type": { "id": 42, "name": "Shots Total", "code": "shots-total", "developer_name": "SHOTS_TOTAL", "model_type": "statistic", "stat_group": "offensive" } }, { "id": 154543328, "fixture_id": 19134600, "type_id": 80, "participant_id": 6, "data": { "value": 427 }, "location": "away", "type": { "id": 80, "name": "Passes", "code": "passes", "developer_name": "PASSES", "model_type": "statistic", "stat_group": "overall" } } ] }, "subscription": [ { "meta": { "trial_ends_at": "2024-10-16 14:50:45", "ends_at": null, "current_timestamp": 1734483733 }, "plans": [ { "plan": "Enterprise plan (loyal)", "sport": "Football", "category": "Advanced" }, { "plan": "Enterprise Plan", "sport": "Cricket", "category": "Standard" }, { "plan": "Formula One", "sport": "Formula One", "category": "Standard" } ], "add_ons": [ { "add_on": "All-in News API", "sport": "Football", "category": "News" }, { "add_on": "pressure index add-on", "sport": "Football", "category": "Default" }, { "add_on": "Enterprise Plan Predictions", "sport": "Football", "category": "Predictions" }, { "add_on": "xG Advanced", "sport": "Football", "category": "Expected" } ], "widgets": [ { "widget": "Sportmonks Widgets", "sport": "Football" } ] } ], "rate_limit": { "resets_in_seconds": 66, "remaining": 2986, "requested_entity": "Fixture" }, "timezone": "UTC" }
Explanation
HttpURLConnection is the core Java class used for HTTP requests, which supports GET, POST, PUT, DELETE, and more HTTP methods.
JsonSlurper parses the JSON response into a Groovy object (Map/List), while JsonOutput.prettyPrint converts the parsed JSON object back to a well-formatted string to enable a clear and readable format.
2.3. Filter Match stats for specific details.
As you can see, we retrieved a lengthy response. However, what do we do if we need to display just a few of them? Using a tool named ‘Filters,’ we can easily narrow down the response to just a few.
By looking through the response, we will filter the response using five different type_ids, namely, 45 [Ball possession], 86 [Shots on Target], 42 [Shots], 80 [Passes], and 56 [Fouls].
We shall append these type_ids, separated by commas, at the end of the previous url in this form: ‘fixtureStatisticTypes:type_id,type_id,…’
import groovy.json.JsonSlurper import groovy.json.JsonOutput import java.net.HttpURLConnection import java.net.URL def fetchData(apiUrl) { try { // Create a connection to the API URL def url = new URL(apiUrl) HttpURLConnection connection = (HttpURLConnection) url.openConnection() connection.setRequestMethod("GET") connection.setConnectTimeout(5000) connection.setReadTimeout(5000) // Read the response def response = connection.inputStream.text // Parse the JSON response def json = new JsonSlurper().parseText(response) // Pretty print the JSON def prettyJson = JsonOutput.prettyPrint(JsonOutput.toJson(json)) println("Data:\n${prettyJson}") return json } catch (Exception e) { println("Error: ${e.message}") } return null } // Provide the API URL def apiUrl = "https://api.sportmonks.com/v3/football/fixtures/19134600?api_token=API_Token&include=statistics.type&filters=fixtureStatisticTypes:45,86,42,80,56" // Fetch and pretty print the data fetchData(apiUrl)
Data: { "data": { "id": 19134600, "sport_id": 1, "league_id": 8, "season_id": 23614, "stage_id": 77471288, "group_id": null, "aggregate_id": null, "round_id": 339246, "state_id": 5, "venue_id": 151, "name": "Manchester City vs Tottenham Hotspur", "starting_at": "2024-11-23 17:30:00", "result_info": "Tottenham Hotspur won after full-time.", "leg": "1/1", "details": null, "length": 90, "placeholder": false, "has_odds": true, "has_premium_odds": true, "starting_at_timestamp": 1732383000, "statistics": [ { "id": 154543270, "fixture_id": 19134600, "type_id": 56, "participant_id": 6, "data": { "value": 9 }, "location": "away", "type": { "id": 56, "name": "Fouls", "code": "fouls", "developer_name": "FOULS", "model_type": "statistic", "stat_group": "defensive" } }, { "id": 154461825, "fixture_id": 19134600, "type_id": 42, "participant_id": 9, "data": { "value": 23 }, "location": "home", "type": { "id": 42, "name": "Shots Total", "code": "shots-total", "developer_name": "SHOTS_TOTAL", "model_type": "statistic", "stat_group": "offensive" } }, { "id": 154543030, "fixture_id": 19134600, "type_id": 80, "participant_id": 9, "data": { "value": 566 }, "location": "home", "type": { "id": 80, "name": "Passes", "code": "passes", "developer_name": "PASSES", "model_type": "statistic", "stat_group": "overall" } }, { "id": 154541889, "fixture_id": 19134600, "type_id": 86, "participant_id": 6, "data": { "value": 7 }, "location": "away", "type": { "id": 86, "name": "Shots On Target", "code": "shots-on-target", "developer_name": "SHOTS_ON_TARGET", "model_type": "statistic", "stat_group": "offensive" } }, { "id": 154541880, "fixture_id": 19134600, "type_id": 86, "participant_id": 9, "data": { "value": 5 }, "location": "home", "type": { "id": 86, "name": "Shots On Target", "code": "shots-on-target", "developer_name": "SHOTS_ON_TARGET", "model_type": "statistic", "stat_group": "offensive" } }, { "id": 154543137, "fixture_id": 19134600, "type_id": 56, "participant_id": 9, "data": { "value": 19 }, "location": "home", "type": { "id": 56, "name": "Fouls", "code": "fouls", "developer_name": "FOULS", "model_type": "statistic", "stat_group": "defensive" } }, { "id": 154511651, "fixture_id": 19134600, "type_id": 45, "participant_id": 9, "data": { "value": 58 }, "location": "home", "type": { "id": 45, "name": "Ball Possession %", "code": "ball-possession", "developer_name": "BALL_POSSESSION", "model_type": "statistic", "stat_group": "overall" } }, { "id": 154511652, "fixture_id": 19134600, "type_id": 45, "participant_id": 6, "data": { "value": 42 }, "location": "away", "type": { "id": 45, "name": "Ball Possession %", "code": "ball-possession", "developer_name": "BALL_POSSESSION", "model_type": "statistic", "stat_group": "overall" } }, { "id": 154461826, "fixture_id": 19134600, "type_id": 42, "participant_id": 6, "data": { "value": 9 }, "location": "away", "type": { "id": 42, "name": "Shots Total", "code": "shots-total", "developer_name": "SHOTS_TOTAL", "model_type": "statistic", "stat_group": "offensive" } }, { "id": 154543328, "fixture_id": 19134600, "type_id": 80, "participant_id": 6, "data": { "value": 427 }, "location": "away", "type": { "id": 80, "name": "Passes", "code": "passes", "developer_name": "PASSES", "model_type": "statistic", "stat_group": "overall" } } ] }, "subscription": [ { "meta": { "trial_ends_at": "2024-10-16 14:50:45", "ends_at": null, "current_timestamp": 1734485131 }, "plans": [ { "plan": "Enterprise plan (loyal)", "sport": "Football", "category": "Advanced" }, { "plan": "Enterprise Plan", "sport": "Cricket", "category": "Standard" }, { "plan": "Formula One", "sport": "Formula One", "category": "Standard" } ], "add_ons": [ { "add_on": "All-in News API", "sport": "Football", "category": "News" }, { "add_on": "pressure index add-on", "sport": "Football", "category": "Default" }, { "add_on": "Enterprise Plan Predictions", "sport": "Football", "category": "Predictions" }, { "add_on": "xG Advanced", "sport": "Football", "category": "Expected" } ], "widgets": [ { "widget": "Sportmonks Widgets", "sport": "Football" } ] } ], "rate_limit": { "resets_in_seconds": 2791, "remaining": 2995, "requested_entity": "Fixture" }, "timezone": "UTC" }
Chapter 3.0 Retrieving only required values.
In this chapter, we modify our code to retrieve only the needed values and include a line naming the home and away teams.
import groovy.json.JsonSlurper def fetchData(apiUrl) { try { // Open the URL connection def url = new URL(apiUrl) HttpURLConnection connection = (HttpURLConnection) url.openConnection() connection.setRequestMethod("GET") connection.setConnectTimeout(5000) connection.setReadTimeout(5000) // Check for a successful response if (connection.responseCode == 200) { // Parse the JSON response def response = connection.inputStream.text def json = new JsonSlurper().parseText(response) // Access the fixture data (home and away team) def fixtureName = json.data?.name if (fixtureName) { def teams = fixtureName.split(" vs ") def homeTeam = teams[0]?.trim() def awayTeam = teams[1]?.trim() def header = String.format("%-25s %-40s %-25s", homeTeam, "----- STATS ----", awayTeam) println header // Access statistics data def statistics = json.data?.statistics if (statistics) { def statsLines = [] statistics.each { stat -> if (stat.location == 'home') { def typeId = stat.type_id def name = stat.type?.name def homeValue = stat.data?.value def awayStat = statistics.find { it.location == 'away' && it.type_id == typeId } def awayValue = awayStat?.data?.value ?: "N/A" // Format the output to align values and names with equal spaces between them def formattedLine = String.format("%-30s %-42s %-30s", homeValue, name, awayValue) statsLines << formattedLine } } // Center-align each stats line relative to the header def headerWidth = header.length() statsLines.each { line -> int padding = Math.max((int) ((headerWidth - line.length()) / 2), 0) // Explicit cast to `int` println " " * padding + line } } else { println "No statistics data found in the response." } } else { println "Fixture name not found in the response." } } else { println "Failed to fetch data. HTTP response code: ${connection.responseCode}" } } catch (Exception e) { println "Error: ${e.message}" } } // API URL def apiUrl = "https://api.sportmonks.com/v3/football/fixtures/19134600?api_token=API_Token&include=statistics.type&filters=fixtureStatisticTypes:45,86,42,80,56" // Fetch and print data fetchData(apiUrl)
Explanation.
Our new code iterates through the statistics for the home and away teams and formats it for neat printing. It also adds the names of both teams right before the stats.
Manchester City | —– STATS —- | Tottenham Hotspur |
23 | Shots Total | 9 |
566 | Passes | 427 |
5 | Shots On Target | 7 |
19 | Fouls | 9 |
58 | Ball Possession % | 42 |
3.1 Creating the Image.
In this section, we will jump through a few hoops to create an image with the text centered properly.
Here is our new piece of code.
import groovy.json.JsonSlurper import java.awt.Color import java.awt.Font import java.awt.Graphics import java.awt.FontMetrics import java.awt.image.BufferedImage import javax.imageio.ImageIO def fetchData(apiUrl) { try { // Open the URL connection def url = new URL(apiUrl) HttpURLConnection connection = (HttpURLConnection) url.openConnection() connection.setRequestMethod("GET") connection.setConnectTimeout(5000) connection.setReadTimeout(5000) // Check for a successful response if (connection.responseCode == 200) { // Parse the JSON response def response = connection.inputStream.text def json = new JsonSlurper().parseText(response) // Access the fixture data (home and away team) def fixtureName = json.data?.name if (fixtureName) { def teams = fixtureName.split(" vs ") def homeTeam = teams[0]?.trim() def awayTeam = teams[1]?.trim() // Access statistics data def statistics = json.data?.statistics if (statistics) { def statsLines = [] // Add header line to statsLines def headerLine = [homeTeam, " TEAM STATS ", awayTeam] statsLines << headerLine // Add statistics data to statsLines statistics.each { stat -> if (stat.location == 'home') { def typeId = stat.type_id def name = stat.type?.name def homeValue = stat.data?.value?.toString() def awayStat = statistics.find { it.location == 'away' && it.type_id == typeId } def awayValue = awayStat?.data?.value?.toString() ?: "N/A" // Group home value, stat name, and away value together for alignment def formattedLine = [homeValue, name, awayValue] statsLines << formattedLine } } // Calculate image dimensions based on content int lineHeight = 40 // Space for each line of text int padding = 20 // Padding around the text int imgWidth = 800 // Fixed width for the image int imgHeight = (statsLines.size() * 2 + 2) * lineHeight + 2 * padding // Account for empty lines // Create the image for drawing text BufferedImage img = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_ARGB) Graphics g = img.createGraphics() // Set background to light grey (#F5F5F5) g.setColor(new Color(245, 245, 245)) // Light grey color (hex: #F5F5F5) g.fillRect(0, 0, imgWidth, imgHeight) // Set font color to black g.setColor(Color.BLACK) // Black text color g.setFont(new Font("Courier New", Font.BOLD, 20)) // Draw the header at the top int yPosition = padding + lineHeight // Starting position after padding // Draw statistics centered under the header yPosition += 2 * lineHeight // Start position after header and spacing statsLines.each { row -> // Calculate the exact x position for each stat (home stat, stat name, away stat) int homeStatX = (imgWidth / 4) - g.getFontMetrics().stringWidth(row[0]) / 2 int statNameX = (imgWidth / 2) - g.getFontMetrics().stringWidth(row[1]) / 2 int awayStatX = (imgWidth * 3 / 4) - g.getFontMetrics().stringWidth(row[2]) / 2 // Draw each stat under the header columns g.drawString(row[0], homeStatX, yPosition) // Home stat column g.drawString(row[1], statNameX, yPosition) // Stat name column g.drawString(row[2], awayStatX, yPosition) // Away stat column yPosition += 2 * lineHeight // Extra space for empty line between stats } // Save the image as a PNG file File outputFile = new File("match_stats_image.png") ImageIO.write(img, "PNG", outputFile) println "Image has been saved as match_stats_image.png." } else { println "No statistics data found in the response." } } else { println "Fixture name not found in the response." } } else { println "Failed to fetch data. HTTP response code: ${connection.responseCode}" } } catch (Exception e) { println "Error: ${e.message}" } } // Helper function to draw centered text def drawCenteredString(Graphics g, String text, int y, int width) { FontMetrics metrics = g.getFontMetrics() int textWidth = metrics.stringWidth(text) int startX = (width - textWidth) / 2 g.drawString(text, startX, y) } // API URL def apiUrl = "https://api.sportmonks.com/v3/football/fixtures/19134600?api_token=API_Token&include=statistics.type&filters=fixtureStatisticTypes:45,86,42,80,56" // Fetch and generate image fetchData(apiUrl)
Image has been saved as match_stats_image.png.
Explanation.
An image is created in memory with a specific width and height in the BufferedImage. The BufferedImage.TYPE_INT_ARGB specifies that the image will support transparency and RGB colour channels.
Graphics: This object provides the drawing tools to render text, shapes, or other graphics onto the image.
g.setColor(new Color(245, 245, 245)); sets the background colour to light grey using the Colour object. The fillRect method fills the entire image area with the chosen colour (starting at (0, 0) and covering the entire width and height).
Saving the Image: After drawing all the text, the image is saved as a PNG file to disc with the name match_stats_image.png. Ensure you have the necessary permission to save to the folder on your workstation.
The image is saved to the same folder where you have created your Groovy file.
Chapter 4: Making Our Code Dynamic.
In this chapter, we shall modify our code to print an image for any fixture of your choice.
We will do so by including the line: def fixtureId = System.console()?.readLine() ?: new Scanner(System.in).nextLine() which will force us to provide a fixture ID at runtime.
We will make our URL dynamic to accept any ID of choice by modifying our URL.
def apiUrl = “https://api.sportmonks.com/v3/football/fixtures/${fixtured}?api_token=…..”
import groovy.json.JsonSlurper import java.awt.Color import java.awt.Font import java.awt.Graphics import java.awt.image.BufferedImage import javax.imageio.ImageIO def fetchData(apiUrl) { try { // Open the URL connection def url = new URL(apiUrl) HttpURLConnection connection = (HttpURLConnection) url.openConnection() connection.setRequestMethod("GET") connection.setConnectTimeout(5000) connection.setReadTimeout(5000) // Check for a successful response if (connection.responseCode == 200) { // Parse the JSON response def response = connection.inputStream.text def json = new JsonSlurper().parseText(response) // Access the fixture data (home and away team) def fixtureName = json.data?.name if (fixtureName) { def teams = fixtureName.split(" vs ") def homeTeam = teams[0]?.trim() def awayTeam = teams[1]?.trim() // Access statistics data def statistics = json.data?.statistics if (statistics) { def statsLines = [] // Add header line to statsLines def headerLine = [homeTeam, " TEAM STATS ", awayTeam] statsLines << headerLine // Add statistics data to statsLines statistics.each { stat -> if (stat.location == 'home') { def typeId = stat.type_id def name = stat.type?.name def homeValue = stat.data?.value?.toString() def awayStat = statistics.find { it.location == 'away' && it.type_id == typeId } def awayValue = awayStat?.data?.value?.toString() ?: "N/A" // Group home value, stat name, and away value together for alignment def formattedLine = [homeValue, name, awayValue] statsLines << formattedLine } } // Calculate image dimensions based on content int lineHeight = 40 // Space for each line of text int padding = 20 // Padding around the text int imgWidth = 800 // Fixed width for the image int imgHeight = (statsLines.size() * 2 + 2) * lineHeight + 2 * padding // Account for empty lines // Create the image for drawing text BufferedImage img = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_ARGB) Graphics g = img.createGraphics() // Set background to light grey g.setColor(new Color(245, 245, 245)) // Light grey color g.fillRect(0, 0, imgWidth, imgHeight) // Set font color and style g.setColor(Color.BLACK) g.setFont(new Font("Courier New", Font.BOLD, 20)) // Draw the statistics int yPosition = padding + lineHeight statsLines.each { row -> int homeStatX = (imgWidth / 4) - g.getFontMetrics().stringWidth(row[0]) / 2 int statNameX = (imgWidth / 2) - g.getFontMetrics().stringWidth(row[1]) / 2 int awayStatX = (imgWidth * 3 / 4) - g.getFontMetrics().stringWidth(row[2]) / 2 g.drawString(row[0], homeStatX, yPosition) g.drawString(row[1], statNameX, yPosition) g.drawString(row[2], awayStatX, yPosition) yPosition += 2 * lineHeight } // Save the image File outputFile = new File("match_stats_image.png") ImageIO.write(img, "PNG", outputFile) println "Image has been saved as match_stats_image.png." } else { println "No statistics data found in the response." } } else { println "Fixture name not found in the response." } } else { println "Failed to fetch data. HTTP response code: ${connection.responseCode}" } } catch (Exception e) { println "Error: ${e.message}" } } // Prompt the user for the fixture ID println "Please enter the fixture ID:" def fixtureId = System.console()?.readLine() ?: new Scanner(System.in).nextLine() // API URL def apiUrl = "https://api.sportmonks.com/v3/football/fixtures/${fixtureId}?api_token=API_Token&include=statistics.type&filters=fixtureStatisticTypes:45,86,42,80,56" // Fetch and generate image fetchData(apiUrl)
Please enter the fixture ID: 19154581 Image has been saved as match_stats_image.png.
Using the fixture ID for the game between Bayern Munich and Bayer Leverkusen in the Bundesliga, 19154581, we can print out an image with the match stats just like we did for Manchester City vs. Tottenham.
Go ahead and give it a try with any other fixture ID from your current plan.
CHAPTER 5: Adding Your Logo.
In this chapter, our final, we shall add a logo to the generated image so it appears branded. Just like you might have guessed, we shall use our company’s logo, Sportmonks. However, there will be no need to let you know that you are at liberty to replace this with yours.
// Import statements remain unchanged import groovy.json.JsonSlurper import java.awt.Color import java.awt.Font import java.awt.Graphics import java.awt.image.BufferedImage import javax.imageio.ImageIO import java.io.File def fetchData(apiUrl) { try { // Open the URL connection def url = new URL(apiUrl) HttpURLConnection connection = (HttpURLConnection) url.openConnection() connection.setRequestMethod("GET") connection.setConnectTimeout(5000) connection.setReadTimeout(5000) // Check for a successful response if (connection.responseCode == 200) { // Parse the JSON response def response = connection.inputStream.text def json = new JsonSlurper().parseText(response) // Access the fixture data (home and away team) def fixtureName = json.data?.name if (fixtureName) { def teams = fixtureName.split(" vs ") def homeTeam = teams[0]?.trim() def awayTeam = teams[1]?.trim() // Access statistics data def statistics = json.data?.statistics if (statistics) { def statsLines = [] // Add header line to statsLines def headerLine = [homeTeam, " TEAM STATS ", awayTeam] statsLines << headerLine // Add statistics data to statsLines statistics.each { stat -> if (stat.location == 'home') { def typeId = stat.type_id def name = stat.type?.name def homeValue = stat.data?.value?.toString() def awayStat = statistics.find { it.location == 'away' && it.type_id == typeId } def awayValue = awayStat?.data?.value?.toString() ?: "N/A" // Group home value, stat name, and away value together for alignment def formattedLine = [homeValue, name, awayValue] statsLines << formattedLine } } // Calculate image dimensions based on content int lineHeight = 40 // Space for each line of text int padding = 20 // Padding around the text int gapAfterLogo = 60 // Reduced gap between the logo and the content int imgWidth = 720 // Fixed width for the image int imgHeight = (statsLines.size() * 2 + 4) * lineHeight + 2 * padding + gapAfterLogo // Adjust height // Create the image for drawing text BufferedImage img = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_ARGB) Graphics g = img.createGraphics() // Set background to light grey g.setColor(new Color(245, 245, 245)) // Light grey color g.fillRect(0, 0, imgWidth, imgHeight) // Set font color and style g.setColor(Color.BLACK) g.setFont(new Font("Courier New", Font.BOLD, 20)) // Load and draw the logo with aspect ratio preserved int logoHeight = 200 // Maximum height for the logo int logoWidth = 200 // Maximum width for the logo int actualLogoHeight = 0 // Store the actual logo height try { File logoFile = new File("logo.png") // Assume logo.png is in the same directory if (logoFile.exists()) { BufferedImage logo = ImageIO.read(logoFile) int originalLogoWidth = logo.getWidth() int originalLogoHeight = logo.getHeight() // Scale logo to fit within the defined width and height, maintaining aspect ratio double scaleFactor = Math.min(logoWidth / (double) originalLogoWidth, logoHeight / (double) originalLogoHeight) int scaledLogoWidth = (int) (originalLogoWidth * scaleFactor) actualLogoHeight = (int) (originalLogoHeight * scaleFactor) // Center the logo horizontally int logoX = (imgWidth / 2) - (scaledLogoWidth / 2) int logoY = padding // Place the logo at the top g.drawImage(logo, logoX, logoY, scaledLogoWidth, actualLogoHeight, null) } else { println "Logo file not found in the current directory. Skipping logo." } } catch (Exception e) { println "Error loading logo: ${e.message}" } // Draw the statistics int yPosition = padding + actualLogoHeight + gapAfterLogo // Start right below the logo with reduced gap statsLines.each { row -> int homeStatX = (imgWidth / 4) - g.getFontMetrics().stringWidth(row[0]) / 2 int statNameX = (imgWidth / 2) - g.getFontMetrics().stringWidth(row[1]) / 2 int awayStatX = (imgWidth * 3 / 4) - g.getFontMetrics().stringWidth(row[2]) / 2 g.drawString(row[0], homeStatX, yPosition) g.drawString(row[1], statNameX, yPosition) g.drawString(row[2], awayStatX, yPosition) yPosition += 2 * lineHeight } // Save the image File outputFile = new File("match_stats_image.png") ImageIO.write(img, "PNG", outputFile) println "Image has been saved as match_stats_image.png." } else { println "No statistics data found in the response." } } else { println "Fixture name not found in the response." } } else { println "Failed to fetch data. HTTP response code: ${connection.responseCode}" } } catch (Exception e) { println "Error: ${e.message}" } } // Prompt the user for the fixture ID println "Please enter the fixture ID:" def fixtureId = System.console()?.readLine() ?: new Scanner(System.in).nextLine() // API URL def apiUrl = "https://api.sportmonks.com/v3/football/fixtures/${fixtureId}?api_token=API_Token&include=statistics.type&filters=fixtureStatisticTypes:45,86,42,80,56" // Fetch and generate image fetchData(apiUrl)
Please enter the fixture ID: 19154581 Image has been saved as match_stats_image.png.
In our new piece of code, we defined the dimensions by setting a maximum width and height (200×200) for the logo and initialised actualLogoHeight to track its scaled height. We then loaded the logo named ‘logo.png’ from the current directory, ensuring it exists.
We scaled it proportionally and positioned it before drawing the logo on our image. We also made slight adjustments to ensure the content is spaced moderately.
CONCLUSION.
Do you think you can make further improvements to the piece of code? How about adding club logos to replace the club names? If you think you are ready for the challenge, don’t hesitate to give it a try.
There’s plenty more you can do with Sportmonks Football API. Why don’t you take a look at our official documentation to know more?
FAQ
- Create an account on My Sportmonks and get immediate access to our free plan.
- Subscribe to one of our paid plans and receive a one-time-only 14-day free trial.