• Portretfoto van Thijs Kramer
    Thijs Kramer

The road to a React application for Rijkswaterstaat

Today we've released the 2.0 version of the Roadworks application for Rijkswaterstaat. And proud we are!

A litte background information

Rijkswaterstaat (The Dutch Directorate-General for Public Works and Water Management) is the executive agency of the Ministry of Infrastructure and Water Management, and with that comes the responsibility for disclosing correct and up-to-date traffic information. Our assignment was to create an application that could disclose that information in a user-friendly manner.

It started in spring 2015

The first version of this application dates back to the spring of 2015. Popular frontend frameworks around that time were Angular, KnockoutJS, Vue and Backbone, but those weren't as tempting as React, with its modular component architecture. We decided to start using React, a relatively new framework built by Facebook. We initialized the package.json, installed React 0.13, and started building components. Handy packages we started using around then were Reflux, React Router, Immutable, and some polyfills like promise, or fastclick.

Many iterations (and almost 2 years) later the 1.0 version of the application was finished: a standalone React app that even worked on browsers like IE10 and the built-in browser on Windows Mobile 7. Users were able to search for roadworks, obstructions or incidents within a certain time period on all Dutch roads that are managed and maintained by Rijkswaterstaat.

De oude applicatie op een iPad

It restarted in 2019

Frontend frameworks evolve quite rapidly. And that made the first version of this Roadworks app after a few years difficult to maintain. Furthermore, the Dutch Traffic Information Service (Verkeerinformatiedienst, VID) merged with Rijkswaterstaat, and new requirements for the app popped up. Besides traffic info, they wanted to provide context-dependent information, traffic cameras, and news articles in the Roadworks application. Therefore they decided to let us rebuild the application we've built earlier. This time we were also asked to build a backend, suitable for managing news articles, traffic forecasts and traffic cameras. On top of that, API calls to the underlying traffic information system (Ibera) should be made better manageable regarding caching, rate limiting and should only be accessible from the frontend.

The Django backend

We built the API using a vanilla Django installation with Django Rest Framework on top. Content can be managed through the Django Admin, like traffic forecasts and news articles. For proxying requests to the underlying traffic information system, we built a thin wrapper around the requests class, that adds the necessary authentication headers and caches the response:

def get(self, url):
    api_url = f"{settings.RWS_API_URL}{url}"
    cache_key = api_url
    response = cache.get(cache_key)
    if response:
        return response
    get_request = requests.get(api_url, headers=self.get_headers(api_url))
    get_request.raise_for_status()
    cache.set(cache_key, get_request, 60)
    return get_request

This enabled that view methods could be simply written like this:

def roadworks(self, request):
    client = IberaClient()
    response = client.get("/api/roadworks")
    response_as_json = json.loads(response.content)
    return Response(response_as_json)

The React Frontend

We were 100% sure that we wanted to use React for the frontend. React has evolved to a much more mature framework in the last couple of years, with a much richer ecosystem of packages around it than back in 2015. Moreover, we gained much more experience with React over the last couple of years as well. That made it quite clear: we were going to use React. *[CRA]: Create React App The application is mobile-first designed by Eight Media, so we had a good schematical overview of what we had to make.

We started by invoking create-react-app (CRA), and started to build the foundations of the application. We wanted to make the application easy to maintain, so we tried to use as much widely used components as we could: We installed Redux for storing data, Leaflet for mapping purposes, Styled Components for styling components, React Router for routing and Axios for making async requests to our API. We even hadn't ejected from CRA until the very end of the project, because we didn't have to deviate from the standard configuration.

:::sidenote What is ejecting? To start with rapid development, the people from Facebook have developed a tool called 'create-react-app'. It provides a fully configured environment with reasonable defaults That configuration has been hidden by default so that developers can focus on actual development. When ejecting, all those configuration comes available to the developer, and thus can be modified. :::

Early days you had to write a class-based component if you wanted to use state, like toggling a button. With the release of React 16.8 in February 2019, a new React feature was introduced that made development of components cleaner, easier to read, but most of all much more fun to write: React Hooks.

Without React Hooks:

class Tabs extends React.Component {
  constructor() {
    this.state = {
      tab: "route"
    }
  }

  setTab = (tab) => {
    this.setState({ tab });
  }

  render() {
    const { tab } = this.state;
    return (
      <TabList>
        <Tab onClick={() => this.setTab("route")} selected={tab === "route"}>
          Route
        </Tab>
        <Tab onClick={() => this.setTab("wegnummer")} selected={tab === "wegnummer"}>
          Wegnummer
        </Tab>
      </TabList>
    );
  }
}

With React Hooks:

function Tabs() {
  const [tab, setTab] = React.useState("route");

  return (
    <TabList>
      <Tab onClick={() => setTab("route")} selected={tab === "route"}>
        Route
      </Tab>
      <Tab onClick={() => setTab("wegnummer")} selected={tab === "wegnummer"}>
        Wegnummer
      </Tab>
    </TabList>
  );
}

Routes and obstructions are displayed on a Leaflet map, with tiles loaded from PDOK (Publieke Dienstverlening Op de Kaart), a platform that makes governmental geodata publicly accessible. To make things complicated, this data uses a different CRS (Coordinate Reference System) named 'Rijksdriehoeksstelsel'. So we had to tell Leaflet that we wanted to use another CRS for displaying the map tiles. In the first version of the app we used a tool called proj4leaflet to apply the conversion:

const RD = L.CRS.proj4js(
    'EPSG:28992', '+proj=sterea +lat_0=52.15616055555555 +lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 +y_0=463000 +ellps=bessel +units=m +towgs84=565.2369,50.0087,465.658,-0.406857330322398,0.350732676542563,-1.8703473836068,4.0812 +no_defs',
    new L.Transformation(1, 285401.920, -1, 903401.920)
);

Luckily for us, someone built a package to do the conversion so we only had to invoke yarn add leaflet-rd.

The Widget

Search

The final component that we've delivered is a widget: a standalone searchbox that can be embedded on other websites that Rijkswaterstaat manages. We had to eject from CRA to create a derivative of the original build process, so that a zipfile with only the bare necessities gets created when we click the 'build' button.

RWSVerkeersinfo.nl

It took us several months to deliver this app, but we're proud that we've built it! So we would like to congratulate Rijkswaterstaat with the release of RWS Verkeersinfo!

We love code

Cookies

Wij maken gebruik van cookies. Meer hierover lees je in onze Privacy- cookieverklaring.