Submit Your Article Forum Rules

Page 2 of 4 FirstFirst 1234 LastLast
Results 11 to 20 of 31

Thread: Extracting and displaying EXIF data with PHP

  1. #11
    Administrator weegillis's Avatar
    Join Date
    Oct 2003
    Posts
    5,785
    Aside: We can see from this generated HTML that our page, while valid HTML5, is not AAA compliant. "Map Reference" is a repeated link phrase that points to multiple URL's. Fail. Some refinement here will be warranted, but this is for another day.

    Update: Another day has come. AAA refinement implemented. See above edited method post containing this resolved issue.

    At this point we have geo-position data, and a range of maps to view it (on WMO, the Map Type menu offers a whole list of them). More than that would be well beyond the scope of this page.

    Now we move on to the single frame page gps_exif.php.

    PHP Code:
    <!DOCTYPE html><html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Geo-position of a photo subject</title>
    <meta name="robots" content="noindex,nofollow">
    <link rel="stylesheet" href="gps_exif.css">
    </head>
    <body>
    <?php 
     
    require_once "gps_exif_inc.php"
     
    $image = isset($img) ? $img ".jpg" null;
     
    readExif($image); 
     
    ?> 
    <div id="header"><div id="breadcrumb"><a href="./" title="Back to Thumnail page">&lt;&lt;&lt; Thumbnails</a></div><div class="headerbox">Where was this picture taken?</div></div>
    <div id="image"><div class="imagebox"><div class="imageshow"><?php getImg($image); ?></div></div></div>
    <div id="exif">
     <div class="exifbox"> 
      <div class="exifboxtable">  
       <table>
    <?php print_data(); ?> 
       </table>   
      </div>  
     </div> 
    </div>
    </body>
    </html>
    Not much to this one, either, but a couple of things to talk about.
    Last edited by weegillis; 06-03-2012 at 03:59 PM. Reason: Aside resolved in earlier edited post / zindx moved to include

  2. #12
    Administrator weegillis's Avatar
    Join Date
    Oct 2003
    Posts
    5,785
    For this page I put the PHP right in the body for reasons that will reveal themselves, presently. We import the methods include, and test the REQUEST_URI for the presence of &img in the query string. If the variable exists, we copy its value into $image and tack '.jpg' on the end to restore the resource name.

    We also test for the presence of a hidden variable, &h, which is our map view altitude adjust, ranging in value from -4 to +4. These adjust values are arbitrary, just as was setting the initial &z= to 17. In my experiments I've found that z=21 is the upper limit (smallest scale) and z=13 is a large enough scale to show several miles around the target. This is a suitable range for our purposes, and validating ensures we are always generating reasonable requests to WMO.

    The validation method can probably be moved to the methods include file... on another day. {update: done, see #19}

    Armed with $image the page makes a call to readExif(). As it resides in the global scope, passing $image in is kind of unnecessary, in this instance. But it does make the method a little more enclosed; and, closure/re-use is always a good thing.
    Last edited by weegillis; 06-03-2012 at 04:06 PM. Reason: update / revised text

  3. #13
    Administrator weegillis's Avatar
    Join Date
    Oct 2003
    Posts
    5,785
    From within our page, there are three function calls:

    1) readExif($image);

    2) getImg($image);

    3) print_data();

    Let's start with the middle one, since it's the image view portion of these methods, and fairly straight forward. I say fairly, because there is some explanation needed.

    PHP Code:
    function getImg($img) {
     global 
    $exif;
     if (
    file_exists($img)) {
      
    $str "<img src=\"$img\" ";
      
    $chtm =$exif['COMPUTED']['html'];
      if (!
    $chtm) {
       
    $fil_wid $exif['COMPUTED']['Width'];
       
    $fil_hgt $exif['COMPUTED']['Height'];
       
    $str .= "width=\"" . (($fil_wid 0) ? $fil_wid "100%") . "\" height=\"" . (($fil_hgt 0) ? $fil_hgt "100%") . "\"";
      } else {
       
    $str .= $chtm;
      }
      
    $str .= " alt=\"" strTrunc($img4) . "\">";
      echo 
    $str;
     } else {
      echo 
    "Image not found.";
     }

    Last edited by weegillis; 06-03-2012 at 05:36 PM. Reason: refined the method to reduce repetition / correction

  4. #14
    Administrator weegillis's Avatar
    Join Date
    Oct 2003
    Posts
    5,785
    We have the method to test for the 'COMPUTED' section, but for this method I just jump right in and ask for the ['COMPUTED']['html']. If it exists, its value is stored in $chtm. It will look like this:

    Code:
     width="720" height="538"
    This can be handily inserted in our generated IMG tag. If it does not exist, we fall back to the computed width and height, and if they end up not existing, we fall back on width="100%" and height="100%" and let the browser sort it out. The alternate text in our example is the image file name, truncated of its extension.

    Again, we only pass the image name to the method, and extract EXIF data directly from the $exif array.

  5. #15
    Administrator weegillis's Avatar
    Join Date
    Oct 2003
    Posts
    5,785
    Aside: It follows that as a web application, we are limited by connection speeds and bandwidth, and while we are able to work with files of any size, the two to three MB files coming from cameras today are way beyond practical for use online. Remember, the index page requests every image in the folder.

    For this reason we take those huge files and scale them to something a little more manageable, like 720 or less by 720 or less. This is the standard for Facebook, so I adopted it for use in this setting. Quality is set to 80% and EXIF data is left intact (of course).

    It is at this stage in the process that we can add a JPEG Comment to the file. We'll see shortly how these can be displayed along with the picture.

    File size falls in the range of 68 to 118 KB. It would take 20-25 of these files to equal one bulky camera file. That tells us we get 20X the performance with optimized files.

    And it helps with AAA compliance (fast loading pages).
    Last edited by weegillis; 06-02-2012 at 09:43 PM.

  6. #16
    Administrator weegillis's Avatar
    Join Date
    Oct 2003
    Posts
    5,785
    Now we get to the hard part, which will require a lot of explanation, the readExif() method. This method centers itself around GPS data mostly, but also checks for a COMMENT section. Only Error strings are generated within this method, and all created/changed variables reside in the global scope with exception to $img (the argument) and $gps, the volatile results holder in the sequence of expressions. Let's have look under the hood:
    PHP Code:
    function readExif($img) {
     global 
    $exif$gps_alt$lat_dec$lon_dec$use_com$errStr;
     
    $errStr "";
     if (
    file_exists($img)) {
      if (@
    exif_read_data($img)) {
       
    $exif = @exif_read_data($img0true);
       echo 
    "<div id=\"exifdump\">\n";
        
    print_r($exif);
       echo 
    "</div>\n";
       
    $use_com = (hasSection('COMMENT')) ? $exif['COMMENT'][0] : null;
       if (
    hasSection('GPS')) {
        
    $gps $exif['GPS']['GPSAltitudeRef'];
        
    $alt_ref = ($gps !== null) ? $gps null;
        
    $gps $exif['GPS']['GPSAltitude'];
        
    $gps_alt = ($gps !== null) ? round(divide($gps), 4) : "N/A";
        
    $gps getGPS();
        if (
    $gps != null) {
         
    $lat_dec $gps[0];
         
    $lon_dec $gps[1];
        }
        
    normalize();
       } else {
        
    $errStr .= "    <tr>\n<td colspan=\"3\"><p>No GPS tags found.<p></td>\n";
       }    
      } else {
       
    $errStr .= "    <tr>\n<td colspan=\"3\"><p>No EXIF tags found.</p></td>\n";
      }
     }

    Last edited by weegillis; 06-13-2012 at 03:42 AM. Reason: grouped global into a single list / @exif

  7. #17
    Administrator weegillis's Avatar
    Join Date
    Oct 2003
    Posts
    5,785
    Without knowing a most effective way to test for EXIF data, I went with just calling it, and letting that be the TRUE that lets the whole process get underway. Allowing that, a genuine call is made to the exif_read_data() function. Whoa! What is this?

    PHP Code:
       echo "<div id=\"exifdump\">\n";
       
    print_r($exif);
       echo 
    "</div>\n"
    Yes, it is what you see. Debugging code woven right into a display: none; div tag. This is why I had to load the PHP inside the body element, so I could apply CSS to it. What we have here is the complete dump of the EXIF data collection, but we must View Source to read it. It's all nicely laid out for us by PHP, to boot, not the sort of thing you would see if it were displaying in the page, that's certain.

    Now we create a user comment string if one exists in the $exif array, and move on to GPS. We require AltituteRef to determine if the geo-location is above or below sea level. We don't want to assume anything. I just wish the standard would have been 1 and -1, instead of 1 (below) and 0 (above). Then we could test for zero value rather than null. It would be more elegant.

    Here is where $gps takes on its first assignment:
    PHP Code:
       $gps $exif['GPS']['GPSAltitudeRef'];
       
    $alt_ref = ($gps !== null) ? $gps null
    Then it gets another assignment. Note that we re-use the borrowed divide() function:
    PHP Code:
       $gps $exif['GPS']['GPSAltitude'];
       
    $gps_alt = ($gps !== null) ? round(divide($gps), 4) : "N/A"
    And finally the assignment as our results array:
    PHP Code:
       $gps getGPS(); 
    This is where the 'my math' statement comes back into play. I'm pretty sure that the normalization is correct, but not certain. Here is the method I came up with that seems to hit the nail right on the head for most coordinates generated:
    PHP Code:
    function normalize() {
     global 
    $lat_min$lat_sec$log_min$log_sec
     
    $x = (int)$lat_min;
     
    $y = (int)$log_min;
     
    $lat_sec round(($lat_min $x) * 602);
     
    $log_sec round(($log_min $y) * 602);
     
    $lat_min $x;
     
    $log_min $y;

    Last edited by weegillis; 06-03-2012 at 05:38 PM. Reason: notes touch up

  8. #18
    Administrator weegillis's Avatar
    Join Date
    Oct 2003
    Posts
    5,785
    The first two assignments to $gps generate strings, the first either a zero (or a one) or null, the second either a value rounded to four decimal places, or a string reading "N/A". The first false sets a null flag, and the second false creates the error string. We will test these during the output stage. The latter assignment to $gps is the array holding the decimal latitude and longitude.

    The purpose of normalizing is to generate sexagesimal values for degrees, minutes and seconds. With the seconds being stored in the decimal fraction of the minutes, we cannot display the seconds properly. Normalizing removes the decimal fraction from the minutes, and calculates and stores the normalized value in the seconds variable, where we want them. I round them to two decimal places.

  9. #19
    Administrator weegillis's Avatar
    Join Date
    Oct 2003
    Posts
    5,785
    And finally, the last function call from our single frame page, and the one that will output (if they exist) the user comment in a caption below the image frame, and the GPS data that's available, as well as a map reference link out to WikiMapia.org. We'll refer to this link again in our closing discussion.

    print_data() is the HTML factory for the page (excluding the IMG tag already generated). All of the little bits that have been floating around out in the global scope will now get put in their proper place. We have created the variable $z which holds the calculated value for &z= cast as a string, for use in generating the WMO link. Note we added $h to the global list:

    PHP Code:
    function print_data() {
     global 
    $errStr$exif$gps_alt$lat_deg$lat_min$lat_sec$lat_hem$lat_dec$log_deg$log_min$log_sec$log_hem$lon_dec$zindx$use_com$h;
     
    $str "";
     if (
    $use_com) { $str .= "    <tr>\n     <td colspan=\"3\">\n      <p class=\"comment\">$use_com</p>\n     </td>\n    </tr>\n"; }
     if (!
    $errStr) {
      
    $str .= "    <tr><th scope=\"col\">LATITUDE</th><th scope=\"col\">LONGITUDE</th><th scope=\"col\">ALTITUDE</th></tr>\n    <tr>\n";
      
    $str .= "     <td>\n      <p>$lat_deg&deg; $lat_min&rsquo; $lat_sec&rdquo; $lat_hem</p>\n      <p>$lat_dec&deg;</p>\n     </td>\n";
      
    $str .= "     <td>\n      <p>$log_deg&deg; $log_min&rsquo; $log_sec&rdquo; $log_hem</p>\n      <p>$lon_dec&deg;</p>\n     </td>\n";  
      
    $z = isset($h) ?  ((intval($h) <= && intval($h) >= -4) ? "&amp;z=" . (string)(17 intval($h)) : "&amp;z=17") : "&amp;z=17";    
      
    $str .= "     <td>\n      <p>" . (($gps_alt == "N/A") ? $gps_alt "$gps_alt m " . (($alt_ref) ? "Below" "Above") . " Sea Level.") . "</p>\n      <p>Go to this <a href=\"http://www.wikimapia.org/#lat=" $lat_dec "&amp;lon=" $lon_dec $z "\">location on the map</a></p>\n     </td>\n";  
     } else {
      
    $str .= $errStr;
     }
     
    $str .= "    </tr>";
     echo 
    $str;

    Last edited by weegillis; 06-03-2012 at 05:12 PM. Reason: $zindx -> $z / validation moved from html page / init str

  10. #20
    Administrator weegillis's Avatar
    Join Date
    Oct 2003
    Posts
    5,785
    All pretty much straight forward. We can see the Sea Level reference is tested, and where $zindx gets inserted in the link's href. To make use of our hidden adjust feature (that will take JavaScript and possibly AJAX to fully incorporate in the page) we simply add &h= to the URL in the location bar and hit enter (not F5) to re-request the page. Nothing on the page will change but if you examine the link, you will see the new z= value.

    Eg. gps_exif.php?img=IMG0189&h=3

    The URL in the status bar should read, ... &z=20 ...

    That's a wrap. This will continue to be a subject of some study for the next while, but will no doubt gradually fade from the radar in my development pursuits. I've learned a lot this week, and hope you, the reader will be able to take this away with you for use on your own site. Attribution to me (Roy Pierce) would be appreciated, with a link back to this thread.

    Should you have any questions about this script, please bring them up on this forum not somewhere else on the web. Thanks for indulging.

    The latest version of this project is available in the attached ZIP.
    Attached Files Attached Files
    Last edited by weegillis; 06-03-2012 at 10:19 PM. Reason: Removed superfluous comments

Page 2 of 4 FirstFirst 1234 LastLast

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •