Creating a Golang Vessel Map Using Google Maps API

API Knowledge zone, Maritime API

Golang Vessel Map project using Google Maps and Datalastic

In this tutorial, you’ll learn how to create a Golang vessel map that shows real-time ship location on a map using Google Maps API and Datalastic Vessel tracking API. This article will show you a step-by-step tutorial about how to create an interactive vessel map using the Go programming language.

With Go and Datalastic API, you can easily create a live map that displays vessel routes, their current locations, and other important information in real-time. Whether you’re a shipping company or simply interested in vessel tracking, this tutorial will provide you with the knowledge and tools to create a vessel’s live map with Go and the Datalastic API. We’ll cover everything from setting up your development environment to importing necessary libraries and making the necessary components to render the map.

So, let’s dive in and create a vessel map that will allow you to track vessel routes and their locations with ease.

Setting Up Your Golang Vessel Map Project

The idea of the application is simple and exciting. A user can navigate through a map and if the user clicks on some point on the map, the application will put on the map all the vessels in a radius of 50 nautical miles around the point. Every displayed vessel also shows available info like vessel name, speed, course, MMSI, and coordinates.

As a map engine, we will use Google Maps with its JavaScript API. The platform datalastic.com with its incredible API will provide us with information about vessels’ real-time positions and status. The backend is responsible for hosting static files and handling API requests. This part will be implemented using the Go programming language.

To summarize all the steps we will need to perform to get the map working we will need to do the following:

  • Set Up the Project
  • Get the Datalastic API Key
  • Acquire the Google Maps JavaScript API Key
  • Implement the Golang Backend
  • Host Static Files with the Embed Filesystem
  • Handle API Requests
  • Integrate the Google Maps JavaScript API
  • Enable User Navigation
  • Display Vessel Information

Prerequisites To Build a Vessels Map:

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.

What we will need in order to proceed:

    1. Datalastic API KEY (Maritime API). You can easily get your own API key by submitting for the trial subscriptions.
    2. We need Google Maps JavaScript API KEY. You can easily get one if you follow this tutorial. Google Cloud Platform offers a really generous free tier quota for using Google Maps JavaScript API.

It’s time to discuss our scope of work for the project.

First of all, we should let users navigate through Google Maps. We will implement this using the standard HTML, JS, and CSS file pack.

Next, we should host these static files. This part will be handled by the Golang backend using the embed filesystem, introduced in Go version 1.16.

Later, we should handle the user’s clicks on the map and display the nearest vessels. This logic will be handled both on the Go and JS sides.

Importing a Map for Vessel Visualization in GO Programming Language:

We created a working directory inradius-golang and initialized a new Go project in this directory with the command:

go mod init inradius-golang

The first thing we want to do is to show the map to users. For this, we create index.html file inside the new templates directory (why we call the new folder “templates” you will know a bit later). Also, we create index.js and style.css files inside the static folder.

The content of the index.html file:

go mod init inradius-golang

The first thing we want to do is to show the map to users. For this, we create index.html file inside the new templates directory (why we call the new folder “templates” you will know a bit later). Also, we create index.js and style.css files inside the static folder.

The content of the index.html file:

<html>
<head>
 <title>Vessels from Datalastic.com</title>
 <script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>  <link rel="stylesheet" type="text/css" href="static/style.css" />
 <script type="module" src="static/index.js"></script>
 <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
 <div id="map"></div>
 <script
 src="https://maps.googleapis.com/maps/api/js?key=<GM_API_KEY>&callback=initMap"
 defer
 ></script>
</body>
</html> 

We also included polyfill.min.js and axios.min.js 

These libraries are necessary for map rendering and making HTTP requests. Instead of <GM_API_KEY> you should use your Google Maps API KEY.

The content of style.css and index.js files:

#map {
 height: 100%;
}  html,
body {
 height: 100%;
 margin: 0;
 padding: 0;
}
let map;  function initMap() {
 map = new google.maps.Map(document.getElementById("map"), {
 zoom: 2,
 zoomControl: true,
 scaleControl: true,
 center: { lat: 0, lng: 0 },
 });
}  window.initMap = initMap; 

index.js file contains initMap() functions which initialize Google Maps map.

To host all these files we will use Golang standard library HTTP server.

For this we create main.go file in the project root directory with the following content:

package main  import (
"embed"
"html/template"
"log"
"net/http"
"os"
)  //go:embed static
var static embed.FS  //go:embed templates
var templates embed.FS  var indexTemplate *template.Template  var gmAPIKey string  func main() {
gmAPIKey = os.Getenv("GM_API_KEY")  staticFS := http.FS(static)
staticFileServer := http.FileServer(staticFS)  var err error
indexTemplate, err = template.ParseFS(templates, "templates/index.html")
if err != nil {
 log.Fatalln(err)
}  mux := http.NewServeMux()
mux.Handle("/static/", staticFileServer)
mux.HandleFunc("/", handleIndex)  http.ListenAndServe(":8080", mux)
}  func handleIndex(w http.ResponseWriter, r *http.Request) {
_ = indexTemplate.Execute(w, gmAPIKey)
} 

You may notice a few features not discussed before.

We create a new embed filesystem var static embed.FS contains all files inside static directory. Then we create a new fileserver handler staticFileServer and this handler is accessible by “/static/” route.

We create new embed filesystem var templates embed.FS contains all files inside the templates directory with only one file index.html inside. The reason for naming the directory this way is that we process this file as a template. Instead of hardcoding Google Maps API KEY value in the index.html, we will pass this key value via the env variable GM_API_KEY. Then we get this env variable to gmAPIKey = os.Getenv (“GM_API_KEY”) and render the template using indexTemplate.Execute(w, gmAPIKey). The rendered index.html template is available by “/” route.

The final fix for this step is to turn index.html file into template so this file would be able to get GM_API_KEY value and use it inside the code. For this, we should modify the Google Map initialization script and replate <GM_API_KEY> value with template placeholder {{.}}.

So, it should look like this https://maps.googleapis.com/maps/api/js?key={{.}}&callback=initMap 

Now if we run the application with Go run main.go command and open the browser on-page localhost:8080 we will see an error. This is because we didn’t provide Google Maps API KEY.

To do this we should run it this way:

GM_API_KEY=<your_google_map_api_key> go run main.go

Getting Vessels Coordinates in real-time With Datalastic API:

 

This project is possible due to the existence of the Datalastic platform with its incredible Vessel Tracking API. The API endpoint we will use is called Static Location Tracking and it allows users to specify a geographic point and to see all vessels that are currently inside the radius of the point. To use this endpoint we should send a GET HTTP request to https://api.datalastic.com/api/v0/vessel_inradius and specify the following parameters:

  • Lat — latitude of selected geo-point;
  • Lon — longitude of selected geo-point;
  • Radius — radius of the search area. Expressed in Nautical Miles (NM);
  • Api-key — Datalastic API KEY.
{
 "data": {
 "point": {
 "lat": 41,
 "lon": 1,
 "radius": 50
 },
 "total": 1,
 "vessels": [
 {
 "uuid": "35240a40-82d4-51f8-917e-e7bb7c408bb1",
 "name": "CAMBRILS 28560 F",
 "mmsi": "992241130",
 "imo": null,
 "eni": null,
 "country_iso": "ES",
 "type": "Beacon, Starboard Hand",
 "type_specific": "Beacon, Starboard Hand",
 "lat": 41.06011,
 "lon": 1.060472,
 "speed": null,
 "course": null,
 "heading": null,
 "destination": null,
 "last_position_epoch": 1675029600,
 "last_position_UTC": "2023-01-29T22:00:00Z",
 "eta_epoch": null,
 "eta_UTC": null,
 "distance": 4.53069923894873
 }
 ]
 },
 "meta": {
 "duration": 0.040764131,
 "endpoint": "/api/v0/vessel_inradius",
 "success": true
 }
}

We could use this request directly from JS. But it is extremely important to keep Datalastic API KEY secure, so, we will create a new handler that will wrap Static Location Tracking Datalastic API endpoint.

The new part of Golang code looks like this:

const datalasticBaseURL = "https://api.datalastic.com/api/v0/vessel_inradius?"
var datalasticAPIKey string
...  func main() {
...
datalasticAPIKey = os.Getenv("DATALASTIC_API_KEY")
...
mux.HandleFunc("/api/get-vessels", handleGetVessels)
...
}  func handleGetVessels(w http.ResponseWriter, r *http.Request) {
lat := r.URL.Query().Get("lat")
_, err := strconv.ParseFloat(lat, 64)
if err != nil {
 w.Write([]byte("lat has wrong format"))
 w.WriteHeader(http.StatusBadRequest)
 return
}
lon := r.URL.Query().Get("lon")
_, err = strconv.ParseFloat(lon, 64)
if err != nil {
 w.Write([]byte("lon has wrong format"))
 w.WriteHeader(http.StatusBadRequest)
 return
}  values := url.Values{}
values.Add("lat", lat)
values.Add("lon", lon)
values.Add("radius", "50")
values.Add("api-key", datalasticAPIKey)  resp, err := http.Get(datalasticBaseURL + values.Encode())
if err != nil {
 w.Write([]byte("unable to get vessels"))
 w.WriteHeader(http.StatusInternalServerError)
 return
}
if resp.StatusCode != http.StatusOK {
 w.Write([]byte("unable to get vessels"))
 w.WriteHeader(http.StatusInternalServerError)
 return
}
io.Copy(w, resp.Body)
defer resp.Body.Close()
}

In this code, we create a new handleGetVessels handler that is available by “/api/get-vessels” route. This handler requires two query parameters: lat and lon. Datalastic API KEY is passed the same way as Google Maps API KEY — via env variable DATALASTIC_API_KEY. The radius value is hardcoded to 50 nautical miles.

When handleGetVessels handler is called, it makes the request to Datalastic API and returns a successful response body to the caller.

Now, to run the app we should specify two env variables like here:

DATALASTIC_API_KEY=xxxxxx GM_API_KEY=xxxxx go run main.go

Implementing Vessels Tracking data Display On The Map Using GO:

Now we are ready to implement the designed logic: show vessels when the user clicks some point on the map and display information about the vessel when the user clicks on the one of vessels.

Updated with new logic, static/index.js file looks like this:

 

let map;
let markers = [];
let infoWindow;  function initMap() {
 map = new google.maps.Map(document.getElementById("map"), {
 zoom: 2,
 zoomControl: true,
 scaleControl: true,
 center: { lat: 0, lng: 0 },
 });  infoWindow = new google.maps.InfoWindow();  map.addListener("click", (e) => {
 deleteMarkers()
 addCenterMarker(e.latLng)
 getVessels(e.latLng)
 });
}  function getVessels(latLng) {
 const config = {
 method: 'get',
 baseUrl: window.location.origin,
 url: 'api/get-vessels',
 params: {
 lat: latLng.toJSON().lat,
 lon: latLng.toJSON().lng,
 },
 timeout: 10000,
 }  axios(config).then(function (response) {
 for (let i = 0; i < response.data.data.vessels.length; i++) {
 let vessel = response.data.data.vessels[i];  addVesselMarker(vessel);
 }
 });
}  function addVesselMarker(vessel) {
 const marker = new google.maps.Marker({
 position: {
 lat: vessel.lat,
 lng: vessel.lon,
 },
 map,
 title: vessel.name,
 });
 marker.addListener("click", () => {
 infoWindow.close();
 infoWindow.setContent(
 "<b>Name: </b> " +
 vessel.name +
 "<br><b>MMSI: </b> " +
 vessel.mmsi +
 "<br><b>Lat: </b> " +
 vessel.lat +
 "<br><b>Lng: </b> " +
 vessel.lon +
 "<br><b>Speed: </b> " +
 vessel.speed +
 "<br><b>Course: </b> " +
 vessel.course
 );
 infoWindow.open({
 anchor: marker,
 map,
 });
 });
 markers.push(marker);
}  function addCenterMarker(latLng) {
 const marker = new google.maps.Marker({
 position: latLng,
 map,
 title: "Center",
 });
 marker.addListener("click", () => {
 infoWindow.close();
 infoWindow.setContent(
 JSON.stringify(latLng.toJSON(), null, 2)
 );
 infoWindow.open({
 anchor: marker,
 map,
 });
 });
 markers.push(marker);
}  function deleteMarkers() {
 //We skipped this function to make this code block shorter
}  window.initMap = initMap;

We added infoWindow object of InfoWindow class to show users information about vessels. This object is reused between different vessels.

The most important part is that we added an event listener for the map object. When the user clicks on the map, the callback function does the following:

  • Deletes all previous vessels from the map.
  • Adds new map marker to the center point by addCenterMarker() function. When the user clicks on this marker, the infoWindow displays information about the coordinates of the point;
  • Retrieves all the vessels in the radius and puts them on the map by getVessels() function. getVessels() calls the “/api/get-vessels” endpoint we created before and for every received vessel puts a new marker to the map.

Now our project is ready for launch!

In this article, we combined Google Maps API and Datalastic API to be able to find ships in real-time near some point on the map.

Our project is available on Github, so you can explore the code and see how we combined the power of these APIs to create a user-friendly vessel tracking map. Additionally, we invite you to check out our public Swagger (OpenAPI) docs to learn more about the Datalastic API. And since you read until now I’m sure you’re curious about what the final product looks like, we’ve included a few screenshots of the running app. So why not give it a try and build your own vessel map with Go today?

Final Project Result visualization of vessel map on google maps
Golang Vessel Map project using Google Maps and Datalastic

Read Other Maritime Articles 

Port of Shanghai Details (CN SHG)

Port of Shanghai Details (CN SHG)

The Port of Shanghai is one of the most significant and busiest ports in the world. Located at the mouth of the Yangtze River, this port serves as a vital gateway for international trade. In this article, we will delve into the impressive scale of the port, its...

Worldwide Maritime Companies List

Worldwide Maritime Companies List

The maritime industry, often termed the backbone of global trade, handles over 90% of the world's trade volume. This industry is crucial, as it not only ensures the flow of goods across continents but also contributes to global economic stability. A Worldwide Maritime...

Understanding Ship Casualty Data

Understanding Ship Casualty Data

Every year, the maritime industry faces numerous challenges related to safety at sea. From collisions and groundings to fires and machinery failures, ship casualties not only pose significant risks to human life and the environment but also lead to substantial...

Minimize Costs with Timely Vessel ETA Data

Minimize Costs with Timely Vessel ETA Data

AIn the complex world of global logistics, the Estimated Time of Arrival (ETA) isn't just a figure—it's a crucial strategic element that drives efficiency and cost-effectiveness across the supply chain. Understanding and leveraging vessel ETA information can transform...

How Data Improves Ship Sales and Purchase Decisions?

How Data Improves Ship Sales and Purchase Decisions?

Ship Sales and Purchase drive the shipping industry, one of the world's vital economic engines. Every day, vast volumes of goods are transported across global waterways. The high stakes involved in ship transactions demand precision, efficiency, and informed...