Free IoT-based GPS Tracking Map for ESP32, NodeMCU, and Arduino

Published  January 23, 2025   1
GeoLinker Free IoT-based GPS Tracking Map

In the present world of interconnected devices, there is a lot happening in IoT. Tracking assets, monitoring movement, and analyzing routes are some of the most essential tasks, and they have never been easier, at least on the front end. While basic solutions that you can see in prior projects, such as this Arduino Vehicle Tracker using ESP8266 and IoT-Based GPS Location Tracker using NodeMCU, handle data collection well, using GPS in tracker projects only adds real value when paired with a proper and intuitive UI.

The challenge involved in building a proper IoT-based GPS tracker lies behind the scenes, in the backend infrastructure of the UI and its maintenance. Developing a robust system for capturing, storing, and visualizing GPS data is not always feasible for every individual or organization working on GPS tracker IoT solutions. An ideal solution would be an API that manages all the visualization tasks for IoT tracking devices, where we only need to send the coordinates, and the API itself stores the data in the database and plots the coordinates.

Why Do IoT GPS Tracker Projects Need GeoLinker

When it comes to IoT-based GPS tracker projects, the CircuitDigest Cloud GeoLinker API is near perfect as it offers a free and ready-to-use GPS tracking solution that simplifies location data handling for IoT tracking devices.

GeoLinker combines backend efficiency with an intuitive frontend, providing real-time GPS tracking and visual analytics through an easy-to-navigate interface. Here's a snapshot of its key features:

  • Live location plotting on interactive maps with automatic route tracing

  • Real-time analytics showing distance traveled, average speed, and travel time

  • Auto-refreshing dashboard for uninterrupted monitoring

  • Multi-device support to track several GPS units at once

  • Data history logging with timestamps and accurate coordinates

  • Export options for reports or further analysis

  • Dual-view UI – Map view + Tabular data for flexible exploration

  • Professional dashboard with sortable columns, filters, and map tools

By seamlessly collecting geo-coordinates, storing them efficiently, and presenting them through a feature-rich, user-friendly platform, GeoLinker bridges the gap between technical capability and ease of use, bringing professional-grade GPS tracking within everyone's reach.

What Is CircuitDigest Cloud?

CircuitDigest Cloud platform infographic displaying four main API services: GeoLinker GPS tracking, QR code scanner, license plate recognition ANPR, and SMS messaging tools for developers

CircuitDigest Cloud is an initiative aimed at empowering engineers, makers, and hobbyists with essential tools for rapid prototyping. In addition to the GeoLinker API, we also offer other useful APIs, such as QR Code Scanning, License Plate Recognition, and SMS API, for those interested in expanding their projects. 

To use GeoLinker for your IoT-based GPS tracking system, the first thing you need is an API Key for authentication, which can be obtained while registering an account at CircuitDigest.Cloud. First, let's create an account.

Registering a New Account in CircuitDigest Cloud

GeoLinker web application login page with email and password input fields, registration link, and CircuitDigest Cloud branding header

Step 1: Visit the CircuitDigest.Cloud Home Page. Click the "Login" button located at the top right corner to be redirected to the login page.

Step 2: If you already have an account, log in using your existing credentials. If not, go to the registration page to create an account by filling in the required details. Once completed, click "Register Now" to sign up.

Step 3: After registering, use your email ID and password to log in on the login page. The next step is to generate the API Key for your IoT-based GPS tracker.

Generate API Key

API key generation interface in CircuitDigest Cloud account dashboard showing captcha verification field and generate key button

Step 4: Once logged in, click on "My Account" at the top right corner.

Step 5: You will be directed to a page where you can generate your API Key. Enter the captcha text in the provided box, then click the "Generate API Key" button

Step 6: If the captcha is correct, you'll see a table displaying your API Key along with its expiration date and usage count. By pressing the copy icon near the API Key, your key gets instantly copied to the clipboard.

NOTE: Currently, there is a limit of 100 uses per key for SMS & ANPR APIs and 10,000 data points for GPS tracking. Once you reach this limit, you can generate another key, which provides an additional 100 uses.
This usage limit is in place to prevent server overload.

API Endpoint Configuration

ParameterValue
Base URLhttps://www.circuitdigest.cloud/geolinker
HTTP MethodPOST
Content-Typeapplication/json

Headers

Authorization

  • Type: String

  • Required: Yes

  • Description: API key for secure access (e.g., "rB007iKt0soY")

Content-Type

  • Type: String

  • Required: Yes

  • Description: Must be "application/json"

Request Body Structure

{
  "device_id": "string",
  "timestamp": ["timestamp1", "timestamp2", ...],
  "lat": [lat1, lat2, ...],
  "long": [long1, long2, ...],
  "battery": [battery_level] (optional),
  "payload": [
    {
      "custom_key1": value1,
      "custom_key2": value2
    }
  ] (optional)
}

Required Fields

- device_id: String identifier for the device sending data

- timestamp: Array of timestamp strings in format "YYYY-MM-DD HH:MM:SS"

- lat: Array of latitude coordinates (float values)

- long: Array of longitude coordinates (float values)

Optional Fields

- battery: Array containing battery level percentage (0-100)

- payload: Array of custom key-value pairs for additional sensor data

Data Types and Validation

FieldTypeFormatRangeDescription
timestampArray of strings"YYYY-MM-DD HH:MM:SS

(e.g., "2025-01-09 12:30:45")
-UTC timestamps with configurable timezone offset applied
lat (Latitude)Array of floatsDecimal degrees (e.g., 12.971598) 
 
-90.0 to +90.0Latitude coordinates converted from NMEA format
long (Longitude)Array of floatsDecimal degrees (e.g., 77.594566)-180.0 to +180.0Longitude coordinates converted from NMEA format
battery (Optional)Array of integers-0-100Battery level percentage
payload (Optional)Array of objects--Custom sensor data as key-value pairs

Example Request

{
 "device_id": "GPS_TRACKER_001",
 "timestamp": ["2025-01-09 12:30:45", "2025-01-09 12:31:45"],
 "lat": [12.971598, 12.972341],
 "long": [77.594566, 77.595123],
 "battery": [85],
 "payload": [
   {
     "temperature": 25.6,
     "humidity": 65.2,
     "speed": 45.8
   }
 ]
}

Response Codes

Success Responses
CodeStatusDescription
200OKData successfully received and processed
201CreatedNew device record created and data inserted
 
Error Responses
400Bad RequestInvalid payload format or missing required fields
401UnauthorizedInvalid or missing API key
500Internal Server ErrorServer-side processing error

Validation Rules

  1. Required Fields: All core fields (device_id, timestamp, lat, long) must be present

  2. Array Length Consistency: timestamp, lat, and long arrays must have the same length

  3. Coordinate Bounds: Latitude must be between -90 and +90, longitude between -180 and +180

  4. API Key: A Valid authorization header must be provided

  5. Data Format: Coordinates should not be 0.0, 0.0 (considered an invalid GPS fix)

Setting Up GeoLinker for IoT Tracking Devices

Here is how you set up GeoLinker for your IoT GPS tracker project.

Google Colab interface showing Python test code for GeoLinker V2. The script logs GPS data including latitude, longitude, speed, battery, and timestamp. The console output confirms successful data transmission with a 200 response status, response headers, and a JSON response body stating 'Data saved successfully,' along with a message 'Single data point sent successfully!'

⇒ Data Uploading

Using the API details mentioned above, you can send data from your GPS tracker IoT device to GeoLinker. The data will be plotted, routed, and marked with timestamps, with lots of features added.

GeoLinker GPS tracking dashboard showing a live map centered near Am Timan, Chad, with tracking stats such as distance, time, average speed, and max speed. The interface includes options for viewing trackers, account settings, and system info with auto-refresh.

⇒ Data Visualizing

For visualization, simply log in to your CircuitDigest Cloud ID and open the GeoLinker UI using the Track button provided in the GeoLinker card on the homepage.

Initially, you may see the message "No GPS Found"—this means you need to refresh the page. This is mandatory for the first data point to appear. After that, the page will auto-refresh.

Then, you will be able to see the plotted coordinates on the map.

This page mainly consists of two sections: the map itself and a table that shows the data present in the database.

GeoLinker GPS tracking dashboard showing a detailed live map with multiple trackers (TripA, TripB, Traker A). The interface displays tracking stats including distance, time, average speed, and max speed, along with system info such as last update, waypoints, and auto-refresh. A popup window for TripB shows current location, time, battery, speed, and sensor data including humidity, speed, and temperature.

⇒ User Interface

The GeoLinker map page is straightforward. There are five key areas to understand:

My Trackers sidebar panel in GeoLinker interface displaying device list with All Trackers option and individual GPS device entries

1. My Trackers – This shows a list of your logged devices. These names are the ones you set in the device_id section of the payload.
Clicking on All Trackers will show all devices on the map. If you are specifically looking for a particular device, click on its name.

Tracking statistics panel showing total distance covered, travel time, average speed, and maximum speed calculations for GPS tracking session

2. Tracking Stats – Displays statistics of the data sent.
You can see the Total Distance Covered (based on auto-routing shown on the map), Total Travel Time, Average Speed, and Max Speed.
All values are calculated from the data you’ve sent to the server. You can either view data for all devices or for an individual device.

System information widget displaying last data update timestamp, total waypoints count, and auto-refresh rate configuration options

3. System Info – Shows the last updated data, overall waypoints, and provides an option to adjust the refresh rate of the webpage.

GeoLinker map toolbar showing zoom controls, fullscreen toggle, distance measurement tool, timeline filter, share options, location finder, and map type selector

4. Menus – These are handy and available on the top Right. The options include Zoom In, Zoom Out, Toggle Full Screen, Measure Distance, Time Line Filter, Share GPS Data, Find My Location, Toggle Data Table, Take Screenshot, and  Map Type.

Location details popup displaying GPS coordinates, timestamp, battery percentage, speed data, and device management options including change icon and delete functions

5. Current Location – Shows two main features other than the actual data(Change Icon, Delete Device). The data shown includes the data count, timestamp, coordinates, battery %, speed, and sensor data.

Waypoint information popup showing specific GPS coordinate details with timestamp, location data, and delete waypoint option for individual data points

6. Data Points – Clicking a point shows all data sent at that time, with an option to delete that specific waypoint. 

Testing the GeoLinker using Python

To quickly test the API for your IoT-based GPS tracking system, all you need is:

- An API key

- A few coordinates

- Timestamps

NOTE: A maximum of 10,000 data points is stored. Older points are automatically replaced with new ones following FIFO (First In, First Out) principle when this limit is reached.

Refer to the following code to test the API:

import requests
import json
from datetime import datetime, timedelta
import time
import random
import math
# Configuration
API_KEY = "xxxxxxxxxxxx"
DEVICE_ID = "My Bike"
API_URL = "https://www.circuitdigest.cloud/geolinker"
def send_gps_data(device_id, timestamp, lat, lng, battery, speed=None):
   """
   Send GPS data to the API endpoint and display full response
  
   Args:
       device_id: Unique identifier for the GPS device
       timestamp: Current timestamp for the GPS reading
       lat: Latitude coordinate
       lng: Longitude coordinate
       battery: Battery level percentage
       speed: Speed in km/h (optional)
  
   Returns:
       API response or None if error occurs
   """
   # Prepare payload with sensor data
   payload_data = {
       "temperature": random.uniform(24.0, 32.0),  # Random temperature between 24-32°C
       "humidity": random.uniform(60, 80)          # Random humidity between 60-80%
   }
  
   if speed is not None:
       payload_data["speed"] = speed
  
   # Prepare main data structure for API
   data = {
       "device_id": device_id,
       "timestamp": [timestamp],
       "lat": [lat],
       "long": [lng],
       # "battery": [battery],     # Commented out as per original
       # "payload": [payload_data] # Commented out as per original
   }
  
   headers = {
       "Authorization": API_KEY,
       "Content-Type": "application/json"
   }
  
   try:
       # Display the data being sent
       print(f" Sending data:")
       print(f"     Request Headers: {headers}")
       print(f"     Request Body: {json.dumps(data, indent=2)}")
      
       response = requests.post(API_URL, json=data, headers=headers)
       status_icon = "✓" if response.status_code == 200 else "✗"
      
       # Display the response details
       print(f"{status_icon} {timestamp} | Lat: {lat:.6f} | Lng: {lng:.6f} | Speed: {speed or 0:2.0f} km/h | Battery: {battery}%")
       print(f"     Response Status: {response.status_code}")
       print(f"     Response Headers: {dict(response.headers)}")
      
       try:
           response_json = response.json()
           print(f"     Response Body: {json.dumps(response_json, indent=2)}")
           return response_json
       except:
           print(f"     Response Text: {response.text}")
           return response.text
          
   except Exception as e:
       print(f"✗ Error sending data: {e}")
       return None
def send_bulk_gps_data(device_id, timestamps, latitudes, longitudes, batteries, payloads):
   """
   Send all GPS data in one bulk POST request and display full response
  
   Args:
       device_id: Unique identifier for the GPS device
       timestamps: List of timestamps
       latitudes: List of latitude coordinates
       longitudes: List of longitude coordinates
       batteries: List of battery levels
       payloads: List of payload data
  
   Returns:
       API response or None if error occurs
   """
   data = {
       "device_id": device_id,
       "timestamp": timestamps,
       "lat": latitudes,
       "long": longitudes,
       "battery": batteries,
       "payload": payloads
   }
   headers = {
       "Authorization": API_KEY,
       "Content-Type": "application/json"
   }
   try:
       print(f"\n Sending bulk data with {len(timestamps)} points...")
       print(f" Data size: {len(json.dumps(data))} characters")
      
       # Display the data being sent
       print(f"\n Sending bulk data:")
       print(f"     Request Headers: {headers}")
       print(f"     Request Body (first 3 points):")
       sample_data = {
           "device_id": device_id,
           "timestamp": timestamps[:3],
           "lat": latitudes[:3],
           "long": longitudes[:3],
           "battery": batteries[:3],
           "payload": payloads[:3]
       }
       print(json.dumps(sample_data, indent=2))
       print(f"    (Showing first 3 of {len(timestamps)} points)")
      
       response = requests.post(API_URL, json=data, headers=headers)
      
       print(f"\n Bulk Upload Response:")
       print(f"    Status Code: {response.status_code}")
       print(f"    Response Headers: {dict(response.headers)}")
      
       try:
           response_json = response.json()
           print(f"    Response Body: {json.dumps(response_json, indent=2)}")
          
           if response.status_code == 200:
               print(" Bulk data sent successfully!")
           else:
               print(f" Error in bulk upload: Status {response.status_code}")
              
           return response_json
       except:
           print(f"    Response Text: {response.text}")
           if response.status_code == 200:
               print(" Bulk data sent successfully!")
           else:
               print(f" Error in bulk upload: Status {response.status_code}")
           return response.text
          
   except Exception as e:
       print(f" Exception during bulk upload: {e}")
       return None
def calculate_speed_between_points(lat1, lng1, lat2, lng2, time_diff_minutes):
   """
   Calculate speed between two GPS coordinates using Haversine formula
   """
   if time_diff_minutes <= 0:
       return 0
  
   # Earth's radius in kilometers
   R = 6371
  
   # Convert latitude and longitude from degrees to radians
   lat1_rad = math.radians(lat1)
   lat2_rad = math.radians(lat2)
   dlat = math.radians(lat2 - lat1)
   dlng = math.radians(lng2 - lng1)
  
   # Haversine formula for great-circle distance
   a = math.sin(dlat/2)**2 + math.cos(lat1_rad) * math.cos(lat2_rad) * math.sin(dlng/2)**2
   c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
   distance_km = R * c
  
   # Calculate speed and cap at 80 km/h for realistic city driving
   speed_kmh = (distance_km / time_diff_minutes) * 60
   return min(speed_kmh, 80)
def get_traffic_delay(hour, is_main_road=False):
   """Simulate traffic-based delays"""
   base_delay = 1.0
   if 8 <= hour <= 10:
       base_delay = 1.8 if is_main_road else 2.2
   elif 18 <= hour <= 20:
       base_delay = 2.0 if is_main_road else 2.5
   elif 15 <= hour <= 16:
       base_delay = 1.4 if is_main_road else 1.6
   elif 12 <= hour <= 14:
       base_delay = 1.2
   elif 22 <= hour or hour <= 6:
       base_delay = 0.7
   return base_delay * random.uniform(0.8, 1.3)
def get_journey_data():
   """
   Define the complete journey with GPS coordinates and context information
   """
   return [
       # Starting location and initial movement
       (11.010915301164033, 77.0132087643678, "Home - Journey Start", False),
       (11.010852113196895, 77.01297272998573, "Leaving home area", False),
       (11.011368147865086, 77.01284398395916, "Local road - heading to main road", False),
      
       # Connecting to main roads
       (11.012895183932189, 77.01271523793258, "Approaching main road connection", True),
       (11.013400683781928, 77.0126508649193, "Main road junction", True),
       (11.013432277493726, 77.01226462683955, "Main road - steady progress", True),
      
       # Main journey on primary roads
       (11.012768808834144, 77.00907816268172, "Major road section - good speed", True),
       (11.012831996389947, 77.00888504364185, "Continuing on main road", True),
       (11.01294784020703, 77.00871338227309, "Main road - approaching junction", True),
       (11.01306368397854, 77.00850953439766, "Junction area - slower traffic", True),
      
       # Navigating through complex areas
       (11.013253246415422, 77.0082305846734, "Navigating through junction", True),
       (11.013379621305521, 77.00788726193586, "Post-junction - picking up speed", True),
       (11.013400683781928, 77.00755466803386, "Main road - good traffic flow", True),
       (11.013116340223256, 77.0073830066651, "Approaching destination area", True),
      
       # Arriving at destination
       (11.012789871354238, 77.00712551461193, "Destination area - slower speeds", False),
       (11.012084276110311, 77.00710405694085, "Near destination - parking search", False),
       (11.011483992258128, 77.00720061646078, "Destination reached - parking", False),
      
       # Activity at destination
       (11.010567767176969, 77.007361548994, "Parked - short walk/activity", False),
       (11.010188638723273, 77.00772632940264, "Activity at destination", False),
      
       # Starting return journey
       (11.009999074313392, 77.0082949576867, "Preparing to leave destination", False),
       (11.009588351006704, 77.0090996203528, "Starting return journey", False),
       (11.009630476500412, 77.00924982405049, "Return route - local roads", False),
       (11.009641007872897, 77.00934638357042, "Return route progress", False),
       (11.009672601988104, 77.0094107565837, "Return route - slow section", False),
       (11.009672601988104, 77.0094965872681, "Traffic light/stop", False),
      
       # Return journey main sections
       (11.009806413371894, 77.01042480446029, "Return route - main road access", True),
       (11.009668649605482, 77.00984427871586, "Return via alternate route", True),
       (11.009574719727805, 77.00914254429954, "Return journey - main section", True),
       (11.009317977909296, 77.0091935795298, "Return journey continues", True),
       (11.009023663841996, 77.00925737356766, "Return - steady progress", True),
      
       # Return journey through different areas
       (11.007991322505761, 77.00951042346873, "Return route - different path", True),
       (11.007640648812478, 77.00985491127311, "Return journey - avoiding traffic", True),
       (11.007302498784261, 77.01039078119103, "Return route - side roads", False),
       (11.007026968844638, 77.01119458606792, "Return - residential area approach", False),
       (11.00680779711771, 77.01218339365457, "Return - navigating local roads", False),
      
       # Final approach to home
       (11.00703323089158, 77.01391221208027, "Return - longer route taken", False),
       (11.007678221013876, 77.01345927441155, "Return - heading towards home area", False),
       (11.008417140020423, 77.01333168636238, "Return - approaching home locality", False),
       (11.009037079096183, 77.01320409828668, "Return - familiar roads", False),
       (11.009368965151896, 77.01313392485663, "Return - almost home", False),
       (11.009857400529931, 77.01313392485663, "Return - final approach", False),
       (11.009882448476192, 77.01332530697017, "Return - entering home area", False),
       (11.010045260075, 77.01359962133293, "Return - home locality", False),
       (11.010427240780418, 77.01384203867676, "Return - very close to home", False),
       (11.010665195723483, 77.01434601157575, "Return - final street", False),
       (11.010871840649608, 77.01433325276818, "Return - parking area", False),
       (11.011084747391523, 77.01421842350005, "Return - almost parked", False),
       (11.010959508150215, 77.01355496550643, "Return - final positioning", False),
       (11.01092819833156, 77.01342737743073, "Return - parking maneuver", False),
       (11.010890626544791, 77.0132678923361, "Home - Journey Complete", False)
   ]
def send_bulk_journey_data():
   """
   Prepare all journey data and send in one bulk API call (based on second code)
   """
   print(" BULK DATA UPLOAD MODE")
   print("=" * 60)
  
   journey_data = get_journey_data()
   start_time = datetime.now().replace(hour=14, minute=30, second=0, microsecond=0)
   current_time = start_time
   battery_level = 87
   timestamps, latitudes, longitudes, batteries, payloads = [], [], [], [], []
   print(f" Preparing {len(journey_data)} GPS points for bulk upload...")
  
   for i, (lat, lng, description, is_main_road) in enumerate(journey_data):
      
       if i == 0:
           interval = 0
           speed = 0
       else:
           # Calculate realistic time intervals
           traffic_factor = get_traffic_delay(current_time.hour, is_main_road)
           base_time = random.uniform(45, 120) if not is_main_road else random.uniform(40, 90)
           interval = int(base_time * traffic_factor)
           current_time += timedelta(seconds=interval)
          
           # Calculate speed between points
           time_diff = interval / 60.0
           prev_lat, prev_lng = journey_data[i - 1][0], journey_data[i - 1][1]
           speed = calculate_speed_between_points(prev_lat, prev_lng, lat, lng, time_diff)
           # Apply traffic conditions
           if 8 <= current_time.hour <= 10 or 18 <= current_time.hour <= 20:
               speed *= random.uniform(0.3, 0.6)  # Rush hour traffic
           elif is_main_road:
               speed *= random.uniform(0.7, 1.0)
           else:
               speed *= random.uniform(0.4, 0.8)
       # Battery drain simulation
       battery_drain = random.randint(0, 2) if i % 3 == 0 else 0
       battery_level = max(10, battery_level - battery_drain)
       # Collect data for bulk send
       timestamps.append(current_time.strftime("%Y-%m-%d %H:%M:%S"))
       latitudes.append(lat)
       longitudes.append(lng)
       batteries.append(battery_level)
       payloads.append({
           "temperature": round(random.uniform(25.0, 32.0), 2),
           "humidity": round(random.uniform(60.0, 80.0), 2),
           "speed": round(speed, 1)
       })
       print(f"  {i+1:2d}. {description[:50]:<50} | Speed: {speed:4.1f} km/h | Battery: {battery_level:2d}%")
   print(f"\n Sending all {len(timestamps)} points in one request...")
  
   # Send bulk data
   response = send_bulk_gps_data(DEVICE_ID, timestamps, latitudes, longitudes, batteries, payloads)
  
   # Summary
   print("\n" + "=" * 60)
   print(" BULK UPLOAD SUMMARY:")
   print(f" Total Points: {len(timestamps)}")
   print(f" Journey Time: {start_time.strftime('%H:%M')} - {current_time.strftime('%H:%M')}")
   print(f" Final Battery: {battery_level}%")
   print("=" * 60)
def send_single_gps_data():
   """
   Send a single GPS data point manually with full response display
   """
   print(" Send Single GPS Data Point")
   print("-" * 40)
  
   try:
       # Get user input for GPS data
       lat = float(input("Enter Latitude: "))
       lng = float(input("Enter Longitude: "))
       speed = float(input("Enter Speed (km/h, or 0 if stationary): "))
       battery = int(input("Enter Battery Level (0-100): "))
      
       # Generate current timestamp
       current_time = datetime.now()
       timestamp = current_time.strftime("%Y-%m-%d %H:%M:%S")
      
       print(f"\n Sending GPS data...")
       print(f" Location: {lat}, {lng}")
       print(f" Speed: {speed} km/h")
       print(f" Battery: {battery}%")
       print(f" Time: {timestamp}")
       print("-" * 60)
      
       # Send the data and display full response
       response = send_gps_data(DEVICE_ID, timestamp, lat, lng, battery, speed)
      
       print("-" * 60)
       if response:
           print(" Single data point sent successfully!")
       else:
           print(" Failed to send data")
          
   except ValueError:
       print(" Invalid input. Please enter valid numbers.")
   except KeyboardInterrupt:
       print("\n Operation cancelled by user.")
def run_realistic_journey_simulation():
   """
   Run individual GPS point simulation with delays and full response display
   """
   journey_data = get_journey_data()
   current_time = datetime.now().replace(hour=14, minute=30, second=0, microsecond=0)
   battery_level = 87
  
   print("=" * 100)
   print(" REALISTIC GPS JOURNEY SIMULATION WITH RESPONSE DISPLAY")
   print("=" * 100)
   print(f" Total Points: {len(journey_data)}")
   print(f" Start Time: {current_time.strftime('%Y-%m-%d %H:%M:%S')}")
   print(f" Initial Battery: {battery_level}%")
   print("=" * 100)
  
   for i, (lat, lng, description, is_main_road) in enumerate(journey_data):
      
       if i == 0:
           timestamp = current_time.strftime("%Y-%m-%d %H:%M:%S")
           speed = 0
           delay_seconds = 0
       else:
           # Calculate realistic delay
           traffic_factor = get_traffic_delay(current_time.hour, is_main_road)
           base_time = random.uniform(45, 120) if not is_main_road else random.uniform(40, 90)
           delay_seconds = int(base_time * traffic_factor)
          
           current_time += timedelta(seconds=delay_seconds)
           timestamp = current_time.strftime("%Y-%m-%d %H:%M:%S")
          
           # Calculate speed
           time_diff = delay_seconds / 60.0
           prev_lat, prev_lng = journey_data[i - 1][0], journey_data[i - 1][1]
           speed = calculate_speed_between_points(prev_lat, prev_lng, lat, lng, time_diff)
          
           # Apply traffic conditions
           if 8 <= current_time.hour <= 10 or 18 <= current_time.hour <= 20:
               speed *= random.uniform(0.3, 0.6)
      
       # Battery drain
       battery_drain = random.randint(0, 2) if i % 3 == 0 else 0
       battery_level = max(10, battery_level - battery_drain)
      
       print(f"\n{i+1:2d}. {description}")
       print(f"      Delay: {delay_seconds:3d}s |  Speed: {speed:4.1f} km/h |  Battery: {battery_level:2d}%")
       print("    " + "-" * 80)
      
       # Send GPS data with full response display
       response = send_gps_data(DEVICE_ID, timestamp, lat, lng, battery_level, speed)
      
       # Short delay for demo
       time.sleep(1)
  
   print("\n" + "=" * 100)
   print("REALISTIC JOURNEY SIMULATION COMPLETED!")
   print("=" * 100)
def main():
   """
   Main program entry point with user interface
   """
   print("Enhanced GPS Tracker Simulator with Response Display")
   print("=" * 65)
   print("Options:")
   print("1. Send single GPS data manually (with response display)")
   print("2. Run realistic journey simulation (individual points with responses)")
   print("3. Send all journey data at once (bulk upload with response)")
  
   choice = input("\nEnter your choice (1, 2, or 3): ").strip()
  
   if choice == "1":
       send_single_gps_data()
   elif choice == "2":
       print("\n Starting realistic journey simulation...")
       run_realistic_journey_simulation()
   elif choice == "3":
       send_bulk_journey_data()
   else:
       print("Invalid choice. Please run the program again and select 1, 2, or 3.")
# Run the program when executed directly
if __name__ == "__main__":
   main()

This Python code can be executed locally using any IDE such as Python’s native IDLE or PyCharm. I use Google Colab for these kinds of quick tests, you can try it too.

Once you run the code, the data points will be added to the database, and you’ll be able to view them in the GeoLinker Map UI.

Geolinker_Working_Demo working demonstration

Above, you can see a GIF/video that demonstrates the map plotting of the uploaded data points using the test code.

The last added point is marked by the icon you selected (or a larger round icon), while previous coordinates are shown as smaller connected dots. Most of the UI features are self-explanatory. With this, you’ve successfully learned how to use GeoLinker.

GitHub Repository

code and schematics icondownload icon

Example Projects

This API has been tested using the NEO-6M with NodeMCU, but can be used with any development board capable of handling GPS data and connecting to the internet. While we already have a few IoT GPS Tracker projects that use the GeoLinker API live right now, we will be adding more.

Existing Projects:

Create and Share

We hope this will be useful for quickly testing and deploying your ideas. If you build something using the API, please share it with us, and we will mention your work on this page. Happy building!

Explore Tracking Projects

Here are some of the many location tracking projects that we've built. 

How to build a simple GPS Tracker using ESP32 and Visualize Data on Map

How to build a simple GPS Tracker using ESP32 and Visualize Data on Map

In this tutorial, we’ll build a GPS tracker using the ESP32 microcontroller and the Neo-6M GPS module as the main hardware components, with GeoLinker as the software component.

Build A Low Power SMS Based Vehicle Tracking System with A9G GSM+GPS Module and Arduino

Build A Low Power SMS Based Vehicle Tracking System with A9G GSM+GPS Module and Arduino

Create a GPS tracker using the A9G module and Arduino to monitor real-time location data. This project guides you through interfacing the A9G GSM+GPS module and visualizing coordinates for tracking applications.

GPS module (uBlox Neo 6M) Interfacing with AVR Microcontroller Atmega16/32

GPS module (uBlox Neo 6M) Interfacing with AVR Microcontroller Atmega16/32

Learn how to interface a GPS module with ATmega16/32 AVR microcontroller. This step-by-step tutorial shows how to receive and parse GPS data to display coordinates using UART communication and an LCD.

DIY Location Tracker using GSM SIM800 and Arduino

DIY Location Tracker using GSM SIM800 and Arduino

Build a DIY location tracker using Arduino, GSM SIM800 module, and GPS. This project sends real-time location via SMS, making it ideal for vehicle tracking or personal safety applications.

 

Have any question related to this Article?

Update 1: We are making improvements to GeoLinker API and services might be unstable till 23rd June. Use this thread for any questions or feature updates

 

Update 2:  New features that are tested include, rich maps, multiple device tracking, payload for each data point, automatic tracker updates, mobile optimization 

 

Update 3: The above documentation is currently being updated. You will see the latest API call formats by Monday (June 23 - 2025). 

Add New Comment

Login to Comment Sign in with Google Log in with Facebook Sign in with GitHub