This project is the server-side component of the poster publisher (the UI can be found in HSLdevcom/hsl-map-publisher-ui) that generates the stop posters that you see on public transport stops around the Helsinki area. It uses PostgreSQL as the database where the poster data and generation status is stored, React for rendering the posters, Puppeteer for generating PDF's of the poster app and Koa as the server to tie everything together.
In production everything runs from a docker container that takes care of all dependencies and environment setup.
If ticket zone regions are changed, remember to update ticket-zones-polygons.json to reflect the new zones !
Install dependencies:
yarn
Also install Chrome. Refer the installation instructions of Chrome for your operating system.
This app is split into three parts:
- Server (receives poster requests and stores them into the Redis database)
- Worker (Fetches poster jobs from Redis, fires up Puppeteer to render and save the posters)
- Render (Receives worker's requests for certain poster parameters, fetches relevant data and renders the poster in the browser for Worker's Puppeteer instance to save into PDFs)
- Write CSS styles @ 72 dpi (i.e. 72 pixels will be 25.4 mm)
- Add components to
renderQueue
for async tasks (PDF is generated whenrenderQueue
is empty) - Use SVG files with unique IDs and no style tags (Illustrator exports)
The poster app will try to fit all pieces of the poster in the allotted area, and will drop off or modify the layout as required. This logic is programmed in the StopPoster
component's updateLayout
method. The order is as follows:
- Make the route box full-width
- Hide the route box
- Stretch the timetable column to be full-width
- Hide the tram or route diagram
- Hide the local map (or static SVG image)
It will make two layout passes to check if the map can be rendered. The ads (graphics under the timetable column) will be shown if there is enough space left over under the timetables. They are not included in the logic chain described above.
Each component will add itself to the "render queue" when mounted, an operation of which there are multiple examples in the code. Once the component has finished its own data fetching and layout procedures it removes itself from the render queue. Once the render queue is empty, the poster is deemed finished and the server will instruct Puppeteer to create a PDF of the page. The component can also pass an error when removing itself which triggers the whole poster to fail.
Server and REST API for printing components to PDF files and managing their metadata in a Postgres database.
Create .env file and place your Digitransit apikey there. If you still don't have your own, generate one on https://portal-dev.digitransit.fi or https://portal.digitransit.fi (dev / prod). Without apikey the map won't work and generation will fail.
docker run -p 0.0.0.0:5432:5432 --env POSTGRES_PASSWORD=postgres --name publisher-postgres postgres
Adjust the port if you have many Postgres instances running on your machine. The server needs the PG_CONNECTION_STRING
environment variable set, which it uses to connect to your Postgres instance. If you use the default Postgres port, place this to .env
:
PG_CONNECTION_STRING=postgres://postgres:postgres@localhost:5432/postgres
Again, adjust the port if you are running your Publisher Postgres instance on an other port.
Start Redis
docker run --name redis --rm -p 6379:6379 -d redis
For default configuration, place the following to .env
:
REDIS_CONNECTION_STRING=redis://localhost:6379
In development, start the Publisher backend server like this, (make sure you have connection strings in .env
)
yarn run server
Then, start generator worker. (You can start multiple workers.)
yarn run worker
Finally, start the React app/Rendering
yarn run start
Now you can use the UI with the server, or open a poster separately in your browser. The poster app needs component
and props
query parameters, and the server will echo the currently rendering URL in its console. But if you just need to open the poster app, you can use this link that will show H0454, Snellmaninkatu:
http://localhost:5000/?component=StopPoster&props%5BstopId%5D=1140196&props%5Bdate%5D=2019-09-30&props%5BisSummerTimetable%5D=true&props%5BdateBegin%5D=&props%5BdateEnd%5D=&props%5BprintTimetablesAsA4%5D=false&props%5BprintTimetablesAsGreyscale%5D=false&template=mock_template
You will have to create a template using the Publisher UI. The poster app will download the template from the Publisher server.
If Azure credentials are not set in the .env file the posters will be stored locally.
See hsl-map-publisher-ui for UI.
For fonts you have three options:
- Create
fonts/
-directory inside project folder. PlaceGotham Rounded
andGotham XNarrow
OpenType fonts there from Azure. - Place
AZURE_STORAGE_ACCOUNT
andAZURE_STORAGE_KEY
either via.env.local
or Docker secrets. Fonts will be downloaded from Azure on startup. - If no fonts or credentials are provided, the app use just the default fonts found inside Debian image.
Due to licensing, we cannot include the fonts in the public repository.
Ensure you have the correct environment variables defined in your .env file (by default .env.local
):
Remember to also include DIGITRANSIT_APIKEY
!
Build the local version of hsl-map-publisher
:
docker compose build
Setup the development environment:
docker compose up
Shutdown the environmnet:
docker compose down
You can add the --volumes
flag to delete volumes involved in the compose setup. Especially useful if there's one poster generation erroring and taking a lot of time and holding up rendering.
As before, make sure you are running a database and broker for the publisher:
docker run -p 0.0.0.0:5432:5432 --env POSTGRES_PASSWORD=postgres --name publisher-postgres postgres
docker run --name redis --rm -p 6379:6379 -d redis
Remember to check the naming of the containers! If they are different, use your naming in .env.local
and in next commands. Add also possible credentials to connection strings, if you have set up them, add fill up the variables left empty (CLIENT_SECRET
, API_CLIENT_SECRET
and domain restrictions).
For fonts you have three options:
- Create
fonts/
-directory inside project folder. PlaceGotham Rounded
andGotham XNarrow
OpenType fonts there from Azure. - Place
AZURE_STORAGE_ACCOUNT
andAZURE_STORAGE_KEY
either via.env.local
or Docker secrets. Fonts will be downloaded from Azure on startup. - If no fonts or credentials are provided, the app use just the default fonts found inside Debian image.
Due to licensing, we cannot include the fonts in the public repository.
Build the Docker image with the following command:
docker build --build-arg BUILD_ENV=local -t hsl-map-publisher .
And run the Docker container with these commands (you can leave font-directory mounting away if you don't have them locally):
Server:
docker run -d -p 4000:4000 --name publisher-server -v $(pwd)/output:/output -v $(pwd)/fonts:/fonts --link publisher-postgres --link redis -e SERVICE=server:production hsl-map-publisher
Rendering:
docker run -d -p 5000:5000 --name publisher-render -v $(pwd)/output:/output -v $(pwd)/fonts:/fonts --link publisher-postgres --link redis -e SERVICE=start:production hsl-map-publisher
And finally a Worker, which is linked to the rendering instance:
docker run -d --name publisher-worker -v $(pwd)/output:/output -v $(pwd)/fonts:/fonts --link publisher-postgres --link redis --link publisher-render --link publisher-server -e SERVICE=worker:production hsl-map-publisher
After setting up the local dev environment you can run yarn test
to generate a list of test stops.