This will be a step by step on how I have setup my Storm Chasing Current Location Map here on my website to utilize GPSGate Splitter software and a NMEA GPS puck to transmit my location every 30 seconds to my website and display it on a Google map in real time.
Requirements
You will need a hardware GPS Puck which is sending NMEA data to the computer. The computer need to run the GPSGate Splitter software
Configure GPSGate Splitter
Once GPSGate Splitter is installed, you will want to get the input setup. There are plenty of documents to get it to work with your specific GPS puck. Once you have good input, you will receive a message of “GPS data with valid position” such as this

Setting up Output Tab
Setup the output tab with the URL you will be putting the perl script below. First click on GpsGate.com (Send) and click add.

You will be given a dialogue to enter the URL. Click Connect Options on the next screen.

Now in connect options you need to choose HTTP for the protocol. Under server put in your URL to the perl script and choose 80 for the port. Click OK and then next. For the username and password you will want to make something up. There is an algorithm to figure out the ‘encrypted’ password it will pass in the URL, but I chose to just grab it out of the HTTP logs. I found the PHP snippet below in archives which can be adapted to do a password conversion. I am too lazy to do that, although I am sure Grok would convert it to perl easily.

<?php
$password="PASSWORD STRING PASSED BY GPSGATE";
function invertString($str) {
for ($i=strlen($str) - 1; $i >= 0; $i--) {
$char = substr($str, $i, 1);
if (preg_match("/[0-9]/", $char) > 0) {
$newStr = $newStr . chr(9-(ord($char) - ord("0")) + ord("0"));
}
else if (preg_match("/[a-z]/", $char) > 0) {
$newStr = $newStr . chr((ord("z") - ord("a")) - (ord($char) - ord("a")) + ord("A"));
}
else if (preg_match("/[A-Z]/", $char) > 0) {
$newStr = $newStr . chr((ord("Z") - ord("A")) - (ord($char) - ord("A")) + ord("a"));
}
}
return $newStr;
}
echo invertString($password);
?>
Perl Script
You will notice the $filename variable is the full path to the XML file I want to output. In this case, location1.xml in a document root for a web site.
#!/usr/bin/perl
use CGI;
use XML::Writer;
$filename = '/www/path/to/location.xml';
%auth = ('test1', 'passwordfortest1', 'user2', 'passwordforuser2');
$prein = new CGI;
%in = $prein->Vars;
&format_data;
print "Content-type: text/html\n\n";
if(exists $auth{$username} && $password eq $auth{$username}){
&write_xml_file;
print "Great Success";
}else{
print "Failure";
}
sub write_xml_file(){
my $writer = XML::Writer->new(OUTPUT => 'self', DATA_MODE => 1, DATA_INDENT => 2, );
$writer->xmlDecl('UTF-8');
$writer->startTag('gpsdata');
$writer->startTag('gpsdata',
time => $gps_datetime,
lat => $lat,
lng => $lng,
alt => $alt,
heading => $heading,
direction => $direction
);
$writer->endTag();
$writer->endTag();
my $xml = $writer->end();
open(FH, '>', $filename) or die $!;
print FH $xml;
close(FH);
}
sub format_data(){
# Assign Variables
$lng = $in{'longitude'};
$lat = $in{'latitude'};
$alt = $in{'altitude'};
$speed = $in{'speed'};
$heading = $in{'heading'};
$gps_date = $in{'date'};
$gps_time = $in{'time'};
$username = $in{'username'};
$password = $in{'pw'};
# Validate GPS information for lat & lon
$lng =~ qr/^([+-]?)(?:180(?:\.0+)?|1[0-7]\d(?:\.\d+)?|[0-9]?\d(?:\.\d+)?)$/;
$lat =~ qr/^([+-]?)(?:90(?:\.0+)?|[0-8]?\d(?:\.\d+)?)$/;
# Truncate decimals
$gps_time =~ s/\.\d*//;
$heading =~ s/\.\d*//;
# Convert Altitude from Meters to Feet and truncate decimals
$alt *= 3.28084;
$alt =~ s/\.\d*//;
# Format time correctly
$gps_time =~ s/(\d{2})(\d{2})(\d{2})/$1:$2:$3/;
$gps_date =~ s/(\d{4})(\d{2})(\d{2})/$1-$2-$3/;
# Combine Date and Time for proper output
$gps_datetime = $gps_date . " " . $gps_time;
# Set Heading Direction
$direction = heading_to_direction($heading);
}
sub heading_to_direction {
my ($heading) = @_;
# Ensure heading is between 0 and 360
$heading = $heading % 360;
if ($heading < 0) {
$heading += 360;
}
# Define direction boundaries (16-point compass)
my @directions = qw(N NNE NE ENE E ESE SE SSE S SSW SW WSW W WNW NW NNW);
# Calculate which direction (each direction covers 22.5 degrees)
my $index = int(($heading + 11.25) / 22.5) % 16;
return $directions[$index];
}
This needs to go on a web server running HTTP port 80. HTTPS (443) will not work.
Google Map HTML
<!DOCTYPE html>
<html>
<head>
<title>Ben Holcomb's Location</title>
<meta name="viewport" content="initial-scale=1.0">
<meta charset="utf-8">
<style>
#map {
height: 95%;
}
html, body {
height: 100%;
margin: 0;
padding: 0;
}
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
</head>
<body>
<div id="map"></div>
<div id="update"></div>
<script>
var map;
var markers = [];
function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
center: {lat: 35, lng: -97},
zoom: 10
});
loadMarker();
refresh();
}
function refresh() {
setInterval(function () {
loadMarker();
}, 30000);
};
function deleteMarker() {
for (var i = 0; i < markers.length; i++) {
markers[i].setMap(null);
}
}
function convertUTCDateToLocalDate(date) {
var newDate = new Date(date.getTime()+date.getTimezoneOffset()*60*1000);
var offset = date.getTimezoneOffset() / 60;
var hours = date.getHours();
newDate.setHours(hours - offset);
return newDate;
}
function loadMarker() {
var page = new Date().getTime();
$.ajax({
url: "//www.benholcomb.com/location1.xml",
dataType: 'xml',
data: {page: page},
success: function(data) {
deleteMarker();
ben = data.documentElement.getElementsByTagName("gpsdata");
for (var i = 0; i < ben.length; i++) {
var alt = ben[i].getAttribute("alt");
var marker_image = '//www.benholcomb.com/files/location-icon.png';
var time = ben[i].getAttribute("time");
var localtime = convertUTCDateToLocalDate(new Date(time));
mylat = parseFloat(ben[i].getAttribute("lat"));
mylng = parseFloat(ben[i].getAttribute("lng"));
map.setCenter(new google.maps.LatLng(mylat,mylng));
var image = {
url: marker_image,
size: new google.maps.Size(26, 26),
origin: new google.maps.Point(0, 0),
scaledSize: new google.maps.Size(26, 26)
};
var point = new google.maps.LatLng(
parseFloat(ben[i].getAttribute("lat")),
parseFloat(ben[i].getAttribute("lng")));
var marker = new google.maps.Marker({
map: map,
position: point,
icon: image,
title: time
});
markers.push(marker);
document.getElementById('update').innerHTML = "Last Updated: " + localtime + " (" + time + " UTC) Altitude:" + alt + "ft & your mom";
}
}
});
}
</script>
<script src="https://maps.googleapis.com/maps/api/js?key=[INSERT API KEY HERE]&callback=initMap" async defer></script>
</body>
</html>