Create Vessels Map With React

AIS Tracking, Maritime API

create vessel maps 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.

Create vessels map with React
Final vessel map with React

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 

How Classification Societies Influence Dry Dock Scheduling?

How Classification Societies Influence Dry Dock Scheduling?

In the maritime world, the safety and efficiency of a vessel are paramount. This is where the roles of classification societies and dry dock schedules become pivotal. Both elements are essential cogs in the maritime machinery, ensuring that ships not only meet global...

Dry Dock Schedules Data: Discover Dates & Details

Dry Dock Schedules Data: Discover Dates & Details

In the world of maritime operations, staying ahead in the game means keeping your vessel in top-notch condition. But how can you ensure this without a solid grasp of your ship's maintenance schedule? This is where the importance of understanding ship dry dock...

Dry Dock Dates Data:Navigating Fleet Maintenance

Dry Dock Dates Data:Navigating Fleet Maintenance

In the complex and ever-evolving maritime industry, staying ahead in maintenance schedules is not just about compliance; it's a critical component of operational efficiency and safety. Datalastic's Dry Dock Dates Vessels API emerges as a beacon of innovation, offering...

How To Get Data about Vessel Engines Performance?

How To Get Data about Vessel Engines Performance?

  In the vast and ever-evolving maritime industry, the performance of a vessel's engine stands as a pivotal factor that directly influences its speed, efficiency, and overall operational capacity. Understanding this dynamic is crucial for ship operators, owners,...

Ship Owners Types: A Comprehensive Overview

Ship Owners Types: A Comprehensive Overview

Ship owners play a pivotal role in the maritime industry. They range from individuals to expansive corporations, responsible for hiring crews and managing the ship's journey. In this article we will unveiling how to uncover data about these owners and pinpoint the...