Los Angeles Tennis Court Availability API

Do you live in Los Angeles and book tennis courts through laparks.org? You may have noticed the interface is clunky; pages are slow to load, and you need to load a separate page for each location and date. Wouldn’t it be nice to have a single view of all current availability? In comes the Tennis Court Availability API!

Table Of Contents:

Usage In Google Calendar

The API responds in a variety of formats, including ics, which is generally accepted by Calendar applications like Google Calendar, Apple Calendar, etc.

Unfortunately, Google Calendar will only update the events in the calendar about once every 12 hours. This means you won’t have data as realtime as the API is able to provide. In the future, there may be a more advanced integration that is able to sync events with less delay.

You can formulate a URL that will respond with a set of events you’d like to import to your Google Calendar as an ‘Other Calendar’. If you’d like to see all available court time at Chevoit Hills and Westwood, you could use the URL

https://tenniscalendar.balancingagent.info/v1/latest?locations=chevoit_hills,westwood

Other parameters and parameter values are available, see the full API documentation here. Once you have a URL in mind, you can import the events to your Google calendar as follows:

First, click on the ‘+’ icon beside ‘Other calendars’

Then, select ‘From URL’

Finally, paste your URL into the URL field in ‘Calendar settings’

Feel free to set notification settings, so you can be alerted when a time slot becomes available or unavailable.

Now, anytime you select the Calendar in your Google Calendar view, you will see open court availability side by side with other events on your Calendar.

How It Works

This API is powered by three processes:

recurring python script that collects data

Tennis court availability data is collected by a python script running Selenium to render Javascript and collect HTML from lacity.org SERPs like this one. Because the court availability content is not available in the initial payload, but rather only once Javascript has run, it is necessary to use a browser automation tool like Selenium, instead of simply using Python Requests or PyCurl.

As of now, the process runs each court locations up to every 10 minutes, running at most one at a time. This prevents the process from being too much of a nuisance to lacity.org.

Once the data has been received and parsed, it is uploaded to a Postgresql database using psycopg2. Logs and metrics are monitored using Grafana stack.

A Postgresql Database

The database layer is handled by Postgresql, a flexible, mature, and feature rich open source SQL database. As of now, the database uses asynchronous WAL replication, where write requests all go to a primary server, and read requests are handled by a replica. This means that data is backed up in the event of server failure, and also that the read and write paths do not complete for resources such as CPU, RAM, and network bandwidth.

Metrics are collected via Prometheus postgres_exporter, and logs are collected via Grafana Promtail. There is monitoring and alerting based on server health, available disk space, and integrity of the read and write processes, number of Postgresql connections, response time, and much more. See A Tour Of Infrastructure Dashboards for me info.

A Flask API That Serves Data

The parsing of input parameters, generation of an SQL query, querying of the Postgresql database, and formatting of a response is handled by Python Flask. All endpoints and all possible parameters are handled by a single SQL query with passed in parameters.

Special care has been paid to make the API invulnerable to SQL injection, using strategies such as character escaping and checking an allow list. Logs and metrics are monitored using Grafana stack.

Where It Could Go Next

It would be really nice to be able to reserve court time as well. There is a captcha that needs to be filled in in order to reserve , and so far, the collection script does not make any attempt to fill in that captcha.

If you have a request on how this process could be improved, please feel free to reach out to me at elliot@techenthusiast.info.

Documentation

/v1/latest

Description:

Returns the latest currency exchange rate data. This info changes throughout the day.

Example request:

Example response:

URL Parameters:

  • format
    • Description: The structure of the data in the response.
    • Default: ics
    • Possible Values: ics, json, csv, xml
    • Example: /v1/latest?format=json
  • locations
    • Description: comma separated list of locations to include in the response. ‘*’ indicates all symbols available.
    • Default: ‘*’
    • Possible Values: chevoit_hills, westwood, poinsetta, westchester, griffith_riverside, balboa, van_nuys_sherman_oaks, stoner, mar_vista_parking
    • Example: /v1/latest?locations=chevoit_hills,westwood

Notes:

The response of this endpoint changes throughout the day.

/v1/<date YYYY-MM-DD>/<date YYYY-MM-DD>

Description:

Tennis court availability within a specific date range.

Example request:

Example response:

URL Parameters:

  • format
    • Description: The structure of the data in the response.
    • Default: ics
    • Possible Values: ics, json, csv, xml
    • Example: /v1/2024-09-19/2024-09-21?format=json
  • locations
    • Description: comma separated list of locations to include in the response. ‘*’ indicates all symbols available.
    • Default: ‘*’
    • Possible Values: chevoit_hills, westwood, poinsetta, westchester, griffith_riverside, balboa, van_nuys_sherman_oaks, stoner, mar_vista_parking
    • Example: /v1/2024-09-19/2024-09-21?locations=chevoit_hills,westwood

Posted

in

by

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *