A.B.C.

You are here: Home > Cartography > Geolocalize photos inside a folder with PHP

Geolocalize photos inside a folder with PHP

1. What we want to achieve

The goal to display a marker for any JPG photo inserted into a web folder on a single map. For this, the photo's GPS EXIF information is used.

In our case, it was to display where which plant grows on the island of Formentera, Spain.

The result might look like this:

map with markers

2. Requirements

To be able to achieve this goal, we need the following features:

  • a webserver running PHP code (you could also have XAMPP installed locally to test the whole thing).
  • an open-source JavaScript library for interactive maps: we use the very good Leaflet.js.
  • a map engine that provides the map: we used OSM.
  • at least one photo having GPS metadata (all newer cameras and smartphones have that option).
  • a text editor that colors the code.
  • some JavaScript and PHP knowledge to understand the code ;-)

3. The Code

3.1 Understand the PHP file calls

3.1.1 Get the file list

First of all, we need to get all files located in a folder. For this, we use the following code:

<?php
$dir = 'fotos'; //Give folder name
if (is_dir($dir)) { //Only proceed if this is really a folder
if ($dh = opendir($dir)) { //Open the folder
while (($file = readdir($dh)) !== false) { //Read all objects in the folder
if (substr( $file, 0, 1 ) !== '.') { //Do not take the files beginning with a period (.)
echo $file."<br />"; //Write all file names
}
}
closedir($dh); //Close the folder
}
}
?>

$dir contains the relative path to the folder (here, the folder is located in the same place as the PHP file).

Running that code in your browser returns you the following:

 

3.1.2 Get the EXIF Contents

Then, we need to understand how we can get the EXIF metadata out of a photo. Here we use the PHP function exif_read_data:

<?php
// This code writes all the existing EXIF data
$v_exif = exif_read_data('fotos/west1.jpg', 0, true); //Pass the EXIF content into an array variable
echo "west1.jpg:<br />\n";
foreach ($v_exif as $v_key => $v_section) {
foreach ($v_section as $v_name => $v_val) {
echo "$v_key.$v_name: $v_val<br />\n"; //Write each element from the EXIF
}
}
?>

In the $v_exif you have to define the image name and the path to access it.

If EXIF data is found, the browser displays a huge list:

And if we scroll down, the GPS data will be shown:

What we are interested in are the following lines:

  • GPS.GPSLatitudeRef: N
  • GPS.GPSLatitude: Array
  • GPS.GPSLongitudeRef: W
  • GPS.GPSLongitude: Array

Note that there are arrays for the coordinates; these arrays must be converted to decimal numbers. The Ref information is used to sign the coordinates.

3.1.3 Coordinate Converter

To convert the coordinates, we found some code on stackoverflow.com and adapted it a bit:

//Found on stackoverflow.com
//Generates the signed lat or the signed long coordinate
function f_getGps($exifCoord, $hemi) {
$degrees = count($exifCoord) > 0 ? f_gps2Num($exifCoord[0]) : 0;
$minutes = count($exifCoord) > 1 ? f_gps2Num($exifCoord[1]) : 0;
$seconds = count($exifCoord) > 2 ? f_gps2Num($exifCoord[2]) : 0;
$flip = ($hemi == 'W' or $hemi == 'S') ? -1 : 1;
return $flip * ($degrees + $minutes / 60 + $seconds / 3600);
}

//Found on stackoverflow.com
//Explodes the GPS string into usable float numbers (1° 2' 3.4'' => 1,2,3.4)
function f_gps2Num($coordPart) {
$parts = explode('/', $coordPart);
if (count($parts) <= 0)
return 0;
if (count($parts) == 1)
return $parts[0];
return floatval($parts[0]) / floatval($parts[1]);
}

3.2 Understand the Leaflet.js environment

Once Leaflet.js installed, it's quite easy to produce a map.

In the html's head, we need the following code:

<link rel="stylesheet" href="leaflet/leaflet.css"/>
<script src="leaflet/leaflet.js"></script>

In the body, the map's dimension is defined by this:

<div id="mapid0" style="width: 600px; height: 400px;"></div>

And filled by that:

<script>
//We initialize the map and set the map center coordinate
var osm_map = L.map('mapid0').setView([38.704923, 1.448682], 11);
//We add some tile layers
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/" target="_blank">OpenStreetMap</a> contributors'
}).addTo(osm_map);
//We add 2 markers
var marker = L.marker([38.768885, 1.434996]).addTo(osm_map)
.bindPopup("DSCN9835.JPG").openPopup();
var marker = L.marker([38.735667, 1.444625]).addTo(osm_map)
.bindPopup("P1040877.JPG").openPopup();
</script>

The result looks like this:

In the example above, the map is drawn by a center point and a zoom:

.setView([38.704923, 1.448682], 11)

but it can also be made using boundaries, which is a better solution if we have several markers:

.fitBounds([[38.653219444444, 1.4183111111111], [38.718891666667, 1.5485527777778]])

3.3 Automate the whole stuff

Do you remember the goal we had at the beginning? We want to display a marker on the map for each file located in a web folder. This means that we only want to feed the code with a folder name and all the rest should be done automatically.

In the head, we need, like seen above, those two lines:

<link rel="stylesheet" href="leaflet/leaflet.css"/>
<script src="leaflet/leaflet.js"></script>

And in the body, we define the map's dimensions:

<div id="mapid0" style="width: 600px; height: 400px;"></div>

Here's nothing new.

Then we need to write the PHP code. As we wrote, we define the folder to use:

$my_dir = 'fotos';

We add the two cordinate converter functions:

//Found on stackoverflow.com
//Generates the signed lat or the signed long coordinate
function f_getGps($exifCoord, $hemi) {
$degrees = count($exifCoord) > 0 ? f_gps2Num($exifCoord[0]) : 0;
$minutes = count($exifCoord) > 1 ? f_gps2Num($exifCoord[1]) : 0;
$seconds = count($exifCoord) > 2 ? f_gps2Num($exifCoord[2]) : 0;
$flip = ($hemi == 'W' or $hemi == 'S') ? -1 : 1;
return $flip * ($degrees + $minutes / 60 + $seconds / 3600);
}

//Found on stackoverflow.com
//Explodes the GPS string into usable float numbers (1° 2' 3.4'' => 1,2,3.4)
function f_gps2Num($coordPart) {
$parts = explode('/', $coordPart);
if (count($parts) <= 0)
return 0;
if (count($parts) == 1)
return $parts[0];
return floatval($parts[0]) / floatval($parts[1]);
}

We created a getCoordinate function, which is fed by a file and its path and returns the latitude and longitude coordinates in an array.

//Get a file's geolocalisation
function f_getCoord($v_currFileLoc) {
//Read the file's exif data
$exif = exif_read_data($v_currFileLoc);
//Check that the wanted data exists
if(array_key_exists('GPSLongitude', $exif) && array_key_exists('GPSLatitude', $exif)) {
//If the data exists, generate the coordinates into an array
$photoCoord = array(f_getGps($exif["GPSLatitude"], $exif['GPSLatitudeRef']),f_getGps($exif["GPSLongitude"], $exif['GPSLongitudeRef']));
return $photoCoord;
} else {
//If no data exists, return false
return false;
}
}

The next function will create a multidimentional array containing for each file: its folder, its name, its lat coord, its long coord.

//Get a multidim array containing: folder, filename, lat, long
//Usage: $my_dir = 'fotos';
// print_r(f_fileArray($my_dir));
function f_fileArray($dir) {
$filesArray = array(); //Init the arrays
if (is_dir($dir)) { //Only proceed if this is really a folder
if ($dh = opendir($dir)) { //Open the folder
while (($file = readdir($dh)) !== false) { //Read all objects in the folder
if (substr( $file, 0, 1 ) !== '.') { //Do not take the files beginning with a period (.)
$v_Coord = f_getCoord($dir . '/' . $file); //Get the coordinates' array for each file
if ($v_Coord !== false) { //Proceed only if the file has usable GPS data
$filesArray[] = array("folder" => $dir , "file" => $file , "lat" => $v_Coord[0], "long" => $v_Coord[1]); //Create an array of array containing all info
}
} //End if (substr())
} //End while loop
closedir($dh); //Close the folder
} //End if opendir
return $filesArray; //Return array of folder, file
} else {
return false; //Return FALSE if it's not a folder
} //End if is_dir
}

To get the map boundaries, we need a function that returns the min and max coordinates of all the markers.

//Get an array of the map boundaries
//Usage: $my_dir = 'fotos';
// print_r(f_getMapBoundaries(f_fileArray($my_dir)));
function f_getMapBoundaries($coordArray) {
$mapArray = array();
//This returns the whole array havng the min lat
$lat_min = $coordArray[array_search(min($lat = array_column($coordArray, 'lat')), $lat)];
//This returns the whole array havng the max lat
$lat_max = $coordArray[array_search(max($lat = array_column($coordArray, 'lat')), $lat)];
//This returns the whole array havng the min long
$long_min = $coordArray[array_search(min($long = array_column($coordArray, 'long')), $long)];
//This returns the whole array havng the max long
$long_max = $coordArray[array_search(max($long = array_column($coordArray, 'long')), $long)];
//Create an array having only lat_min,long_min,lat_max,long_max
$mapArray = array($lat_min['lat'],$long_min['long'],$lat_max['lat'],$long_max['long']);
return $mapArray;
}

Finally, we can generate on-the-fly the javascript code needed to fill the map:

//Create the map and add all markers
$filesGPS = array_filter( f_fileArray($my_dir) );
$mapBounds = array_filter(f_getMapBoundaries(f_fileArray($my_dir)));
echo "<script>\n";
echo "var osm_map = L.map('mapid0').fitBounds([[" . $mapBounds[0] . ", " . $mapBounds[1] . "], [" . $mapBounds[2] . ", " . $mapBounds[3] . "]]);\n";
echo "L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {\n";
echo "maxZoom: 18,\n";
echo "attribution: 'Map data &copy; <a href=\"https://www.openstreetmap.org/\" target=\"_blank\">OpenStreetMap</a> contributors'\n";
echo "}).addTo(osm_map);\n";
foreach ($filesGPS as $row) {
$ptcoord = $row['lat'] . "," . $row['long'];
echo "var marker = L.marker([" . $ptcoord . "]).addTo(osm_map)\n";
$pathname = $row['folder'] . "/" . $row['file'];
echo ".bindPopup(\"" . $pathname . "\").openPopup();\n";
}
echo "</script>\n";

We add also all photos below the map (this part is not required) using this code:

  //Display all found images
echo "<br />";
foreach ($filesGPS as $row) {
$ptcoord = $row['lat'] . "," . $row['long'];
$pathname = $row['folder'] . "/" . $row['file'];
echo $pathname . ": " . $ptcoord . "<br />\n"; //Write the file name and its coordinates
echo "<p><img src=\"" . $pathname . "\"></p>"; //Display the image
}

The final result looks like this:

nach oben