A low-code template to help you tell your map-based story through a React-based application. Inspired from Mapbox storytelling
Under the hood it uses the following technologies:
- Frontend tech: React, Deck.gl, Typescript
- Linting: Eslint configs we use within GRAS (@dhi-gras/eslint-config-react and @dhi-gras/eslint-config-ts)
- Rasters: Raster layers are XYZ map tiles, in our case served with Terracotta, but any source can be used.
- Vector layers: Large (many megabytes) vector data should be hosted in some server that either renders or otherwise optimizes the data served to the client. We often use GeoServer for this.
- You must have Node installed which comes with NPM. You can find it here
- Clone the current repository and install the dependencies through
npm
oryarn
. - Run the
start
script and see the app on http://localhost:3000/
The current folder structure implements a package-by-feature approach with a flat structure which separates the features of the application. I will refer to the most important features to get you going easily.
The main components that take care of the stories are the config
, layers
, scroll
, story
and the story-components
.
The scroll feature renders the chapters found within the config
and it creates the story steps based on each chapter. It calculates the height of the scrolling element in order to indicate which step is currently active and therefore, render the right layers on the map.
The story feature takes care of displaying the content found in the content
key of each chapter and filter/remove the layers that need to be added/removed to/from the map.
The functions that take care of the layers are working based on the onStepEnter
and onStepExit
of each chapter and the layer types geojson
, marker
, text-marker
and raster
.
This feature represents the custom stories that appear on each step, in case a customized component must be used. If there is no need of a custom component, you can simply use the type basic
in the content
key of each chapter that takes in a title
, description
or the info
.
The layers feature contains the functions that create the deck.gl layers. At the moment, the current application uses 5 different types of layers - cloropeth
, geojson
, raster
, marker
and marker-text
.
If you need other types of layers and/or want to change the currently existing layer configurations you can find other layers in the deck.gl API reference
Depending on the complexity of the layers that must be added, it will require changes in the config
, layers
and story
features.
- marker layer
The marker layer must be of type marker
and in order to be visible, the visible
key must be true
. It takes in a data
key which is an array of markers with an id
and the coordinates
of the marker. The ids of the markers must use the template {layerMarkerId}-{n}
where n
is the counting if those markers.
- marker text layer
The marker text layer must have the type text-marker
and include in the data
key an array of markers having coordinates
, text
and/or elevation
. For it to be visible on the map, the visible
key must be set to true
. This layer has the option of animating the appearance through a delay of 2s by using the animation
key which must be set to true
.
- geojson layer
The geojson layer must be of type geojson
and include in the data
key either a locally saved geojson file or a link to the file on a remote server. In order to be displayed, it must have the visible
key set to true
.
- raster layer
The XYZ raster layer must be of type raster
and include the url
key as an array of strings - for only one layer, it can have only one item in the array, or more items for multiple rasters under the same layer. For it to be displayed, the visible
key must be set to true
. This layer can also have the animation
key set to true
which basically adds one layer on top of another at an interval of 2 seconds.
- cloropeth layer
The cloropeth layer is of type geojson
and it must include in its id the cloropeth
string. It follows the same specifications as the geojson
layer.
Note: If you are aiming for removing any layer from the active state, the visible
key must be set to false
, either if it's in the onStepEnter
or onStepExit
key of the chapter.
Note: Only one animation type of layer can be added per story step.
Note: Each layer must have a unique id. Based on this id, the filtering functions know what layer to add and what layer to remove
The config feature is the most important one as the stories and the map layers are being rendered based on its specifications. The config file is strongly typed, therefore, following the types from the config/types
should give you a good enough overview of what is expected for each key.
For consitency, the chapter ids must use the string template {configId}-name-of-the-chapter
. This helps if you plan on building an app that contains multiple stories. This way, you can build up a logic based on this string template.
The config file is structured as it follows:
id
- the id of the current story configstyle
- the mapbox style to be added as a basemap. A list of styles can be found herefooter
- the footer that appears at the end of the story. It can be of typecomponent
ortext
.scrollIndicator
- The scrolling animation that appears in the bottom center of the screen and stays fixed while scrolling. It can be of typecomponent
orbasic
chapters
- an array of chapters that represents a story step. One chapter consists of:id
- the id of the chapter following the string template{configId}-name-of-the-chapter
.content
- the component that gets displayed on the current story step. It can be of typecomponent
orbasic
. If a component must be bigger than100vh
, usingvh
units will help the scroll HOC calculate its height easier.location
- the location of where the map should move its viewport when the step changes and it includes thecenter
,zoom
,pitch
and thebearing
.onChapterEnter
- an array of layers that are desired to be added on the current story step. The types of layers that can be added here are found under theconfig/types/layersConfig
file and based on thevisible
key, the app knows if it must remove it or add it.onChapterExit
- an array of layers that are meant to be removed from the map based on thevisible: false
value and the previously added layers with their ids.
If you encounter any problems or have any feature suggestions, feel free to open an Issue and eventually propose a PR.