Contents

IoT Tracking Device - PHP Server & OpenStreetMap (Part 2)

A Tutorial on Writing Coordination to Server Using PHP and Display On the Map

Want some music while reading? 🎵📻

Introduction

This tutorial continues the work from the previous part where we set up the ESP32 to write data to the server.

In this part, we should display the coordination data on the map that we post to the server in the previous post and show the markers that indicate our locations as well. Our group of students quite struggled with the product’s requirements. The teacher expects that we shouldn’t use any map API that requires an API key and the programming language should be PHP for consistency (our server is created with PHP). Therefore, Google Maps shouldn’t be used for this project. This part is another team’s responsibility but I was really curious and I also want to learn PHP. Afterwards, I decided to do this part just for fun but I succeeded 😁. I’m going to show you how to do this.

Requirements

  • XAMPP
  • PhPStorm / VSCode

We need XAMPP for this project and PHP in general because it’s super easy to run PHP code on localhost with XAMPP. After installing XAMPP, navigate to the installation folder and search for “htdocs” folder. You should place this project folder inside, let’s call this folder “madrid-iot”. After that, start Apache server from XAMPP and you could access the project from

http://localhost/madrid-iot/

If you need further information for the setup, please check this video out.

Extract Data

This is the code for extracting the data from the server.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function extractData() {
    global $last_lines;
    global $data;

    $base_urls = array(
        'https://www.hh3dlab.fi/madrid/iot01/file.txt',
        'https://www.hh3dlab.fi/madrid/iot02/file.txt',
        'https://www.hh3dlab.fi/madrid/iot03/file.txt',
        'https://www.hh3dlab.fi/madrid/iot04/file.txt',
        'https://www.hh3dlab.fi/madrid/iot05/file.txt'
    );

    foreach ($base_urls as $key => $base_url) {
        $key += 1;
        $dir = './';
        $url_arr = explode ("/", $base_url);
        $team_no = substr($url_arr[4], -2);

        $filename = 'iotwrite' . $team_no . '.txt';
        $save_location = $dir . $filename;

        getFile($base_url, $save_location);
        $no_of_lines = getNoOfLines($save_location);
        $last_line = getLastLine($save_location, $no_of_lines);
        $coordination = extractLatLong($last_line);
        array_push($last_lines, $last_line);
        array_push($data, $coordination);
    }
}

First of all, we have 2 global variables: last_lines and data. The last_lines variable is for displaying and checking purposes and the data variable contains all of the data that we want to get, specifically all of latitudes and longitudes.

We we an array of base_urls because our group consists of 5 teams and each team will carry a device to track their GPS. Therefore we need to have 5 endpoints to get the data from all of them.

On line 13, we start looping through each base url to:

  1. Get the file from that endpoint
  2. Get the number of lines of that file and extract the last line from the file
  3. Extract latitude and longitude from that last line
  4. Add the data to the data array

From line 14 - 18, we basically just set up the file name and the saving directory for the files. The files will be saved in the current folder in this case.

Get Files From Server

The first step is getting the files from the server so that we could extract to use after that. Here is the code for that

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
function getFile($base_url, $save_location) {
    // Init curl operations
    $curl_session = curl_init($base_url);
    $fp = fopen($save_location, 'wb');
    curl_setopt($curl_session, CURLOPT_FILE, $fp);
    curl_setopt($curl_session, CURLOPT_HEADER, 0);
    curl_exec($curl_session);
    curl_close($curl_session);

    // Close file
    fclose($fp);
}

We simply curl a request using the base url and save the file at the defined saving location.

Extract The Last Line From File

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
function getNoOfLines($save_location) {
    $no_of_lines = 0;
    $fp = fopen($save_location, "r");

    while(!feof($fp)) {
        fgets($fp, 1024);
        $no_of_lines++;
    }

    return $no_of_lines;
}

function getLastLine($save_location, $no_of_lines) {
    if ($no_of_lines > 0) {
        $lines = file($save_location);
        return $lines[$no_of_lines - 2];
    } else {
        return "";
    }
}

Why do we need to get the number of lines? The file will definitely contains many lines because ESP32 will write data to the file every 1 second. So we need to know how many lines there are to get the last line. Then we could extract the last line from all the previous lines, which is the last known location. This line of code seems a little bit strange at first but it’s not

$lines[$no_of_lines - 2]

We know that, for example, in Javascript, if we want to get the last item from an array, it should be [items.length - 1], but in here it’s - 2 because when we download the file, there’s a trailing empty line at the end. Therefore, the code is - 2 not - 1.

Extract Coordination From The Last Line

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
function extractLatLong($last_line) {
    $cleaned_str = preg_replace("/[\s]+/", ",", $last_line);
    $str_arr = explode (",", $cleaned_str);

    if (sizeof($str_arr) > 2) {
        return [
            'latitude' => $str_arr[2],
            'longitude' => $str_arr[3],
            'temperature' => $str_arr[4],
            'pressure' => $str_arr[5],
            'altitude' => $str_arr[6],
        ];
    } else {
        return "There's no valid latitude and longitude data";
    }
}

The data line from the server’s file looks like this

1
03.04.2022 08:22	60.16824642179774,24.931513433662776,temperature,pressure,altitude,0

It starts with the timestamp when the line is written into the file, latitude, longitude, temperate, pressure, altitude and number of satellites.

We’re only interested the later part of the string, therefore, we need to clean up the string first. The first line of the code removes all of the empty space. The second line separates the string into an array by the "," delimiter. And then we save each item of that array into an array that we’re going to use at the end.

Map

So after we have extracted the data from the files, we could now take a look into how to set up the map to display the data. We use OpenStreetMap here because it doesn’t require an API key to manipulate the markers.

The OpenStreetMap API uses Javascript, but we’re using PHP, how can we integrate it into our code? It’s super simple, let’s take a look into the code Below

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<!DOCTYPE html >
<head>
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
    <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
    <link href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" rel="stylesheet"/>
    <title>Madrid Seminar - IoT</title>
    <style>
        ...
    </style>
    <script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"></script>
</head>

<html lang="en">
<body>
    <?php
        include 'extract_data.php';

        if (isset($_POST["Fetch"])) {
            extractData();
        }
    ?>
    <div class="container">
        <form method="POST">
            <input type="submit" value="Fetch Data" name="Fetch" class="fetch-btn">
        </form>
    </div>

    <div id="map"></div>

    <script defer type="text/javascript">
        const data = <?php echo json_encode($data); ?>;

        const mapOptions = {
            center: [60.17116785151859, 24.942630453799243],
            zoom: 14
        }

        const map = new L.map('map' , mapOptions);
        const layer = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png');
        map.addLayer(layer);

        data.slice(0, 5).forEach((c, i) => {
            i += 1;
            const marker = new L.Marker([c.latitude, c.longitude]);
            marker.addTo(map);
            marker.bindPopup(`Team ${i} - ${c.temperature}, ${c.pressure}, ${c.altitude}`).openPopup();
        })
    </script>
</body>
</html>

The code looks a bit messy, but it’s basically HTML and PHP combined with Javascript.

There is a button called “Fetch Data”, every time we click it, it will execute the extractData() function and run all the code mentioned above. The code below is responsible for this

1
2
3
4
5
6
7
<?php
    include 'extract_data.php';

    if (isset($_POST["Fetch"])) {
        extractData();
    }
?>
1
2
3
4
5
<div class="container">
    <form method="POST">
        <input type="submit" value="Fetch Data" name="Fetch" class="fetch-btn">
    </form>
</div>

We know that we have the global variable data that contains an array of the information that we need. For Javascript to read PHP variable, we add this line of code

1
const data = <?php echo json_encode($data); ?>;

After this, we can just loop through the data array, use the information to set latitude and longitude for the markers and they will be rendered on the map.

Conclusion

It was a really fun to learn PHP and at the same time solve a challenge like this. Before this project, I’ve always thought that PHP was outdated somehow but that’s actually wrong. PHP is still used by nearly 80% of all websites and that could be because of the ease of usage and the its versatility.

I was really happy when the teacher called be “guru” after seeing me be able to solve this issue. That was the best compliment I have received so far 😄. Still, I feel that I need to improve my skills a lot more!