Create Vessels Map With React

Create a vessels map with React and visualize vessel tracking data in real-time using vessel and Datalastic APIs.
Vessel maps have become increasingly popular in the maritime industry as it helps with tracking and monitoring vessel movements, and optimizing shipping routes.
Using React, a popular JavaScript library for building user interfaces, we will create an interactive and dynamic vessel map and this article, will be your guide through the process of creating a new service that allow users to track marine vessels on the map in real-time using Datalastic APIs and React.
Introducing the Vessel Tracking Map:
To begin with, the idea of our future web application is to create an interactive map where users can enter vessel identifiers and see their location on the map. Specifically, we will use vessels MMSI as the identifiers (assuming the end user already knows the target vessels’ MMSIs).
Moreover, the source of vessels’ real-time data will be the Datalastic platform, which has an awesome API. You can check out its Swagger (OpenAPI) documentation to know all available API endpoints.
In addition, we plan to use Google Maps service as a map engine and its Google Maps JavaScript API for interacting with it.
Lastly, the entire project is a type of front-end project, which we will implement using React JS library in combination with TypeScript.
Prerequisites For The Map Project:
Before diving into building a vessel’s map, it’s essential to ensure you have the necessary prerequisites in place. This may be familiarizing yourself with the maritime API you’ll be using, and understanding the data you’ll need to incorporate into your map. and for that what we will need is:
1. Google Maps JavaScript API KEY. You can easily get one if you follow this tutorial. Also, Google Cloud Platform offers a really generous free tier quota for using Google Maps JavaScript API.
2. If you (most probably) would like to test this project when it is done, you also need Datalastic API KEY. In case you haven’t one, you can easily get started with one of the trial subscriptions.
It’s time to discuss our scope of work for the project. The tasks will be as follow:
- Create Google Map basis layout;
- Add input form to the layout;
- Retrive information about vessels from Datalastic platform;
- Display vessels markers on the map.
Creating Project Skeleton and Adding Dependencies
To create the project skeleton we use create-react-app library. We also add template flag to tell the library that we will use TypeScript instead of plain JS.
npx create-react-app vessels-reactjs –template typescript
After this we need some additional dependencies. We need @types/googlemaps library to work with Google Maps API. To install it:
npm install –save @types/googlemaps
After this, let’s create Google Maps basis layout.
We don’t need to make any changes in public/index.html or src/index.tsx files. The first working file is src/types/types.tsx where we will declare custom types and interfaces. And we start with this content of this file:
export interface IMap {
mapType: google.maps.MapTypeId;
mapTypeControl?: boolean;
}
export type GoogleLatLng = google.maps.LatLng;
export type GoogleMap = google.maps.Map;
export type GoogleMarker = google.maps.Marker;
We created IMap interface for future Map component. We also declared GoogleLatLng, GoogleMap, GoogleMarker custom types just as a wrappers for types from @types/googlemaps library.
The next step is to create new Map component in src/components/Map/Map.tsx file:
import React from 'react';
import { IMap, GoogleLatLng, GoogleMap, GoogleMarker } from '../../types/types';
import './map.css';
const Map: React.FC<IMap> = ({ mapType, mapTypeControl = false}) => {
const ref = React.useRef<HTMLDivElement>(null);
const [map, setMap] = React.useState<GoogleMap>();
const initMap = (zoomLevel: number, address: GoogleLatLng): void => {
if(ref.current){
setMap(
new google.maps.Map(ref.current,{
zoom: zoomLevel,
center: address,
mapTypeControl: mapTypeControl,
streetViewControl: false,
rotateControl: false,
scaleControl: false,
fullscreenControl: false,
panControl: false,
zoomControl: true,
gestureHandling: 'cooperative',
mapTypeId: mapType,
draggableCursor: 'pointer'
})
)
}
}
const defaultMapStart = ():void => {
const defaultAddress = new google.maps.LatLng(0, 0);
initMap(2, defaultAddress);
}
const startMap = ():void => {
if(!map){
defaultMapStart();
}
}
React.useEffect(startMap,[map]);
return (
<div className="map-container">
<div ref={ref} className="map-container__map"></div>
</div>
);
}
export default Map
Map component first of all should be able to initialize the map layout, ant it uses useEffect hook to initialize the map.
The next file, we bring to the project, is src/utils/mapUtils.tsx file and it contains only one function:
export const loadMapApi = () => {
const mapsURL = `https://maps.googleapis.com/maps/api/js?key=${process.env.REACT_APP_GOOGLE_MAPS_API_KEY}&v=quarterly`;
const scripts = document.getElementsByTagName('script');
for (let i = 0; i < scripts.length; i++) {
if (scripts[i].src.indexOf(mapsURL) === 0) {
return scripts[i];
}
}
const googleMapScript = document.createElement('script');
googleMapScript.src = mapsURL;
googleMapScript.async = true;
googleMapScript.defer = true;
window.document.body.appendChild(googleMapScript);
return googleMapScript;
};
It looks a bit overcomplicated but this construction is necessary to make this entire project more flexible and to not hardcode Google Maps API KEY in the code. Now, if someone wants to launch the project with their own keys, all they need to do is to create .env file in the project root with the content:
REACT_APP_GOOGLE_MAPS_API_KEY=<your_google_maps_api_key>
The last thing we need before we could modify App.tsx file is context. For this, we create file src/context.tsx with the context stub. Of course, we will modify it in the future, but for now it looks like this:
import React from 'react';
export const AppContext = React.createContext({});
Now we can put it all together and update our src/App.tsx file.
import React from 'react';
import { loadMapApi } from './utils/mapUtils';
import Map from './components/Map/Map';
import { AppContext } from './context';
function App() {
const [scriptLoaded, setScriptLoaded] = React.useState(false);
React.useEffect(() => {
loadMapApi();
window.addEventListener('load', function () {
setScriptLoaded(true);
});
}, []);
return (
<AppContext.Provider value={{}}>
<div className="App">
{scriptLoaded && (
<Map
mapType={google.maps.MapTypeId.ROADMAP}
mapTypeControl={true}
/>
)}
</div>
</AppContext.Provider>
);
}
export default App;
In the App we define following behaviour: using useEffect hook it loads Google Maps layout initialization script. As well as using event listener mechanism it catches window load event and sets variable scriptLoaded = true . After this Map component is rendered.
If you try to launch the project, all you will see is just a Google Maps map. Let’s make it more useful.
Retrieving Real-time Vessels Data Using Datalastic API Bulk Tracking Endpoint
To retrieve realtime vessels data we will use Datalastic API bulk tracking endpoint.
In order to use it we should send GET HTTP request with following query:
https://api.datalastic.com/api/v0/vessel_bulk?api_key={YOUR_API_KEY}&{PARAMETER}={PARAMETER_NUMBER} .
This endpoint allows to send multiple similar parameters. In our case we want to search by vessel MMSI number, so, we will send multiple mmsi query parameters.
We consider, that users will input vessel MMSI numbers, separated by comma, into one input field. To deal with this, we create new src/api/url.ts file, containing the logic of constructing query string:
export const createURL = (apiKey: string, mmsi: string) => {
const url = new URL("https://api.datalastic.com/api/v0/vessel_bulk");
url.searchParams.append("api-key", apiKey);
const mmsiArr = mmsi.split(',').map(p => p.trim());
mmsiArr.forEach(mmsi => {
url.searchParams.append("mmsi", mmsi);
})
return url;
}
The next new src/api/api.ts file contains getVessels function, that we will use to fetch data from Datalastic API according to user input:
import { createURL } from './url';
export const getVessels = async(apiKey: string, mmsi: string) => {
try {
const response = await fetch(`${createURL(apiKey,mmsi)}`, {
method: 'GET',
});
if (response.ok) {
const data = await response.json();
return data;
} else {
return await Promise.reject(new Error(response.statusText));
}
} catch (error) {
return 'error';
}
}
Developing Vessel Tracking Feature with Datalastic API and Input Form:
In order to allow users to track vessels in real-time and get maritime data, we need to add a form with two input fields:
1. Datalastic API KEY.
2. Vessels MMSI numbers.
Firstly, we will start with the new components for the form: Input and Button components.
src/components/Input/Input.tsx file:
import React from 'react';
import './input.css';
interface Props {
placeholder: string;
className: string;
}
const Input:React.FC<Props> = ({placeholder, className}) => {
return(
<input type="text" placeholder={placeholder} className={className} />
)
}
export default Input
src/components/Button/Button.tsx file:
import React from 'react';
import './button.css';
interface Props {
text: string;
func: () => void;
}
const Button:React.FC<Props> = ({text, func}) => {
return(
<div>
<button type="button" className="button" onClick={func}>{text}</button>
</div>
)
}
export default Button
Also, we will add new Error component. It will allow us to display modal window with additional information to users, for example, in case if some API request fails to get data.
src/components/Error/Error.tsx file:
import React from 'react';
import Button from '../Button/Button';
import { AppContext } from '../../context';
import './error.css';
const Error:React.FC = () => {
const {setError, errorText} = React.useContext(AppContext);
const closeError = (): void => {
setError(false);
}
return(
<div className="error-container">
<p> {errorText} </p>
<Button text="Close" func={closeError}/>
</div>
)
}
export default Error
Now we are ready to introduce entire Form component in src/components/Form/Form.tsx file:
import React from 'react';
import Input from '../Input/Input';
import Button from '../Button/Button';
import { AppContext } from '../../context';
import { getVessels } from '../../api/api';
import './form.css';
const Form:React.FC = () => {
const {setApiKey, setMMSI, apiKey, mmsi, setVessels, setError, setErrorText} = React.useContext(AppContext);
const saveInputsValues = ():void => {
const apiKey = document.querySelector('.input-key') as HTMLInputElement;
const mmsiOfVessels = document.querySelector('.input-mmsi') as HTMLInputElement;
setApiKey(apiKey.value);
setMMSI(mmsiOfVessels.value);
}
const searchVessels = async() => {
const res = await getVessels(apiKey, mmsi);
if(res === 'error'){
setError(true);
setErrorText('Please, check your api key or vessel mmsi');
setVessels([]);
return;
}
if(res.data.total === 0) {
setError(true);
setErrorText("Vessels with such mmsi aren't found");
setVessels([]);
} else {
setVessels(res.data.vessels);
}
}
return(
<form className="form-container" onChange={saveInputsValues}>
<Input placeholder="Input your Api key here..." className="input-key" />
<Input placeholder="Vessel MMSI here..." className="input-mmsi" />
<Button text="Find" func={searchVessels}/>
</form>
)
}
export default Form
Form component contains two input fiels and button. When user clicks button, searchVessels function is triggered, that sends request to Datalastic API bulk tracking endpoint. In case, that there’s some error the result will be that there is no found vessels, users will see error message as following: Error component.
As you could notice, in the Form component we get a lot of variables from the context. Let’s take a look at how we also updated the src/context.tsx file:
import React from 'react';
import { InitialValue } from './types/types';
const initialValue:InitialValue = {
apiKey: '',
setApiKey: (apiKey) => { },
mmsi: '',
setMMSI: (mmsi) => { },
vessels: [],
setVessels: (vessels) => { },
error: false,
setError: (error) => { },
errorText: '',
setErrorText: (errorText) => { }
}
export const AppContext = React.createContext(initialValue);
And InitialValue type in src/types/types.ts file:
export interface InitialValue {
apiKey: string,
setApiKey: (apiKey: string) => void,
mmsi: string,
setMMSI: (mmsi: string) => void,
vessels: IVessel[],
setVessels: (vessels: IVessel[]) => void,
error: boolean,
setError: (error: boolean) => void,
errorText: string,
setErrorText: (errorText: string) => void
}
To bring new functionality to the UI, we update App component: initialize context variables and add Form and Error components. Now src/App.tsx looks like this:
import React from 'react';
import { loadMapApi } from './utils/mapUtils';
import Map from './components/Map/Map';
import Form from './components/Form/Form';
import { IVessel } from './types/types';
import { AppContext } from './context';
import Error from './components/Error/Error';
function App() {
const [apiKey, setApiKey] = React.useState('');
const [mmsi, setMMSI] = React.useState('');
const [vessels, setVessels] = React.useState<IVessel[]>([]);
const [error, setError] = React.useState(false);
const [errorText, setErrorText] = React.useState('');
const [scriptLoaded, setScriptLoaded] = React.useState(false);
React.useEffect(() => {
loadMapApi();
window.addEventListener('load', function () {
setScriptLoaded(true);
});
}, []);
return (
<AppContext.Provider value={{apiKey, setApiKey, mmsi, setMMSI, vessels, setVessels, error, setError, errorText, setErrorText }}>
<div className="App">
{scriptLoaded && (
<Map
mapType={google.maps.MapTypeId.ROADMAP}
mapTypeControl={true}
/>
)}
{scriptLoaded && (
<Form />
)}
{error && <Error />}
</div>
</AppContext.Provider>
);
}
export default App;
Display vessels on the map
Users may not see inputted vessel location data, which could be problematic if real-time visibility is needed. We’ll add a feature for immediate viewing to solve this. Real-time vessel location information improves user experience and app usefulness.
And here’s an example format of a successful response from the Datalastic API bulk tracking endpoint:
When using the Datalastic API bulk tracking endpoint, a successful response will include information about vessel locations in a structured format. This may include vessel identification numbers, latitude and longitude coordinates, timestamps, and other relevant data points. By utilizing this information, users can track vessel movements, monitor traffic patterns, and make informed decisions related to their business or personal needs.
{
"data": {
"total": 1,
"vessels": [
{
"uuid": "17959a4c-f5a9-ed71-1a42-5bfab00670ef",
"name": "GARGANO",
"mmsi": "235362000",
"imo": "9249403",
"eni": null,
"country_iso": "GB",
"type": "Other",
"type_specific": "Supply Vessel",
"lat": 54.105115,
"lon": 0.016225,
"speed": 4.4,
"course": 332.8,
"heading": 333,
"navigation_status": "Under way using engine",
"destination": "SUNDERLAND",
"last_position_epoch": 1638543058,
"last_position_UTC": "2021-12-03T14:50:58Z",
"eta_epoch": null,
"eta_UTC": null
}
]
},
"meta": {
"duration": 0.007911177,
"endpoint": "/api/v0/vessel_bulk",
"success": true
}
}
To use it, we will create a new IVessel interface in src/types/types.ts file:
export type IVessel = {
uuid: string,
name: string,
mmsi: string,
imo: string,
eni: string,
country_iso: string,
type: string,
type_specific: string,
lat: number,
lon: number,
speed: number,
course: number,
heading: number,
navigation_status: string,
destination: string,
last_position_epoch: number,
last_position_UTC: string,
eta_epoch: number,
eta_UTC: string
}
You could notice that we already used this interface in src/App.tsx file in this expression: const [vessels, setVessels] = React.useState<IVessel[]>([]); . Variables vessels, setVessels are already put to the context. So we can use them in the Map component to display to users.
The final version of src/components/Map/Map.tsx file looks like this:
import React from 'react';
import { AppContext } from '../../context';
import { IMap, GoogleLatLng, GoogleMap, GoogleMarker } from '../../types/types';
import { IVessel } from '../../types/types';
import './map.css';
import Icon from '../../assets/icon.png'; const Map: React.FC<IMap> = ({ mapType, mapTypeControl = false}) => {
const ref = React.useRef<HTMLDivElement>(null);
const [map, setMap] = React.useState<GoogleMap>();
const [markers, setMarkers] = React.useState<GoogleMarker[]>([]);
const {vessels} = React.useContext(AppContext);
let markersArr: GoogleMarker[] = [];
let infoWindow = new google.maps.InfoWindow(); const initMap = (zoomLevel: number, address: GoogleLatLng): void => {
if(ref.current){
setMap(
new google.maps.Map(ref.current,{
zoom: zoomLevel,
center: address,
mapTypeControl: mapTypeControl,
streetViewControl: false,
rotateControl: false,
scaleControl: false,
fullscreenControl: false,
panControl: false,
zoomControl: true,
gestureHandling: 'cooperative',
mapTypeId: mapType,
draggableCursor: 'pointer'
})
)
}
}
const defaultMapStart = ():void => {
const defaultAddress = new google.maps.LatLng(0, 0);
initMap(2, defaultAddress);
}
const startMap = ():void => {
if(!map){
defaultMapStart();
}
}
const setMapOnAll = (map: google.maps.Map | null):void => {
for (let i = 0; i < markers.length; i++) {
markers[i].setMap(map);
}
}
const deleteMarkers = ():void => {
setMapOnAll(null);
markersArr = [];
setMarkers([]);
}
const addMarker = (vessel:IVessel): void => {
const location = new google.maps.LatLng(vessel.lat, vessel.lon);
const marker:GoogleMarker = new google.maps.Marker({
position: location,
map: map,
animation: google.maps.Animation.DROP,
icon: Icon,
title: `Name: ${vessel.name}, mmsi: ${vessel.mmsi}`,
});
const info = `<strong>Name:</strong> ${vessel.name}<br>
<strong>mmsi:</strong> ${vessel.mmsi}<br>
<strong>lat:</strong> ${vessel.lat}<br>
<strong>lon:</strong> ${vessel.lon}`;
marker.addListener("click", () => {
infoWindow.close();
infoWindow.setContent(info);
infoWindow.open(map, marker);
});
markersArr.push(marker);
}
React.useEffect(() => {
deleteMarkers();
vessels.forEach(vessel => {
addMarker(vessel);
});
setMarkers(markersArr);
if(map && vessels.length){
map.setCenter({lat: 0, lng: 0});
map.setZoom(2);
};
},[vessels]);
React.useEffect(startMap,[map]);
return (
<div className="map-container">
<div ref={ref} className="map-container__map"></div>
</div>
);
}
export default Map
Launching our maritime project with vessel tracking and maps:
We have implemented a new useEffect hook in our project for vessel tracking, which efficiently follows changes to the vessel’s variables. With the help of this hook, we can perform the following maritime-related actions:
- Delete all previously displayed vessel markers from the map using vessel tracking.
- Create a new array of vessel markers for better visualization of vessels on maps.
- Display these markers on the map to enhance vessel tracking.
- Return the map to its center view.
To add an enjoyable element to our project, we have incorporated custom icon PNG files instead of using Google Maps standard markers. These custom icons can help identify vessels quickly. Moreover, each vessel marker has an info window containing the vessel’s main information that can be easily accessed by clicking on the marker. This feature enhances the user experience of our vessel tracking project.
Our project is now ready to launch, and we are excited to showcase some screenshots of our working application that will provide a sneak peek into the functionality of our maritime project using vessel tracking and maps.


By following our tutorial, you’ll be able to create your own vessel tracking application using these powerful tools. So why wait? Let’s get started!
In this informative article, we have demonstrated how to create a highly effective and user-friendly web application that enables users to track vessels in real-time using the reliable and trusted Google Maps API and Datalastic API.
If you’re interested in exploring the project further, you can find the complete project on our Github page, which includes detailed documentation and instructions on how to set up and use the application.
And if you’re curious to learn more about Datalastic API and its features, we encourage you to check out our public Swagger (OpenAPI) documentation, which provides comprehensive information on how to use the API and its various endpoints.
You might also like to read: Track ships in your port with API
Read Other Maritime Articles
Flight API
Access real-time, historical and future dates flight API to get flight status, location, delay, route data and more. Flight APIs of Aviation Edge are useful data tools for developers with global flight data and uptime rates of 99.9%. Flight Tracking: Keep tabs...
Flight Tracker API
Get real-time aircraft location and speed data with Aviation Edge Flight Tracker API. The flight tracking data allows you to monitor an aircraft’s real-time location, speed, and status throughout its route. The API uses REST which allows a clear and simple integration...
Flight Schedules API
Flight Schedules API developed by Aviation Edge provides real-time, historical and future airport timetable data. Track departure and arrival schedules of airports worldwide. Alternatively, use an airline filter to track an airline’s flight schedule. Flight number ...
Why the Maritime API is a Must-Have?
Maritime API, a revolution in the shipping and logistics sector, have transformed the way businesses in this industry operate. They grant access to a wealth of data about ships, their journeys, and intricate details that can be leveraged to streamline operations....
Find Ships In New York Port
Finding ships in the New York Port is akin to tapping into the pulse of global commerce. Serving as the eastern gateway to the U.S., the NYC port is a bustling hub of trade. Its terminals show how important it is for worldwide links. In this article, we will identify...
Find 15 Major Maritime Ports in the World with API
Find major ports with API: In the world of maritime transportation, ports play a crucial role as key hubs for global trade and commerce. Identifying the major ports is essential for various stakeholders, including shipping companies, logistics providers, and port...
Recent Comments