I would like to extract the GPS EXIF tag from pictures using php.
I'm using the exif_read_data() that returns a array of all tags + data :
GPS.GPSLatitudeRef: N
GPS.GPSLatitude:Array ( [0] => 46/1 [1] => 5403/100 [2] => 0/1 )
GPS.GPSLongitudeRef: E
GPS.GPSLongitude:Array ( [0] => 7/1 [1] => 880/100 [2] => 0/1 )
GPS.GPSAltitudeRef:
GPS.GPSAltitude: 634/1
I don't know how to interpret 46/1 5403/100 and 0/1 ? 46 might be 46° but what about the rest especially 0/1 ?
angle/1 5403/100 0/1
What is this structure about ?
How to convert them to "standard" ones (like 46°56′48″N 7°26′39″E from wikipedia) ? I would like to pass thoses coordinates to the google maps api to display the pictures positions on a map !
This is my modified version. The other ones didn't work for me. It will give you the decimal versions of the GPS coordinates.
The code to process the EXIF data:
$exif = exif_read_data($filename);
$lon = getGps($exif["GPSLongitude"], $exif['GPSLongitudeRef']);
$lat = getGps($exif["GPSLatitude"], $exif['GPSLatitudeRef']);
var_dump($lat, $lon);
Prints out in this format:
float(-33.8751666667)
float(151.207166667)
Here are the functions:
function getGps($exifCoord, $hemi) {
$degrees = count($exifCoord) > 0 ? gps2Num($exifCoord[0]) : 0;
$minutes = count($exifCoord) > 1 ? gps2Num($exifCoord[1]) : 0;
$seconds = count($exifCoord) > 2 ? gps2Num($exifCoord[2]) : 0;
$flip = ($hemi == 'W' or $hemi == 'S') ? -1 : 1;
return $flip * ($degrees + $minutes / 60 + $seconds / 3600);
}
function 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]);
}
This is a refactored version of Gerald Kaszuba's code (currently the most widely accepted answer). The result should be identical, but I've made several micro-optimizations and combined the two separate functions into one. In my benchmark testing, this version shaved about 5 microseconds off the runtime, which is probably negligible for most applications, but might be useful for applications which involve a large number of repeated calculations.
$exif = exif_read_data($filename);
$latitude = gps($exif["GPSLatitude"], $exif['GPSLatitudeRef']);
$longitude = gps($exif["GPSLongitude"], $exif['GPSLongitudeRef']);
function gps($coordinate, $hemisphere) {
if (is_string($coordinate)) {
$coordinate = array_map("trim", explode(",", $coordinate));
}
for ($i = 0; $i < 3; $i++) {
$part = explode('/', $coordinate[$i]);
if (count($part) == 1) {
$coordinate[$i] = $part[0];
} else if (count($part) == 2) {
$coordinate[$i] = floatval($part[0])/floatval($part[1]);
} else {
$coordinate[$i] = 0;
}
}
list($degrees, $minutes, $seconds) = $coordinate;
$sign = ($hemisphere == 'W' || $hemisphere == 'S') ? -1 : 1;
return $sign * ($degrees + $minutes/60 + $seconds/3600);
}
According to http://en.wikipedia.org/wiki/Geotagging, ( [0] => 46/1 [1] => 5403/100 [2] => 0/1 ) should mean 46/1 degrees, 5403/100 minutes, 0/1 seconds, i.e. 46°54.03′0″N. Normalizing the seconds gives 46°54′1.8″N.
This code below should work, as long as you don't get negative coordinates (given that you get N/S and E/W as a separate coordinate, you shouldn't ever have negative coordinates). Let me know if there is a bug (I don't have a PHP environment handy at the moment).
//Pass in GPS.GPSLatitude or GPS.GPSLongitude or something in that format
function getGps($exifCoord)
{
$degrees = count($exifCoord) > 0 ? gps2Num($exifCoord[0]) : 0;
$minutes = count($exifCoord) > 1 ? gps2Num($exifCoord[1]) : 0;
$seconds = count($exifCoord) > 2 ? gps2Num($exifCoord[2]) : 0;
//normalize
$minutes += 60 * ($degrees - floor($degrees));
$degrees = floor($degrees);
$seconds += 60 * ($minutes - floor($minutes));
$minutes = floor($minutes);
//extra normalization, probably not necessary unless you get weird data
if($seconds >= 60)
{
$minutes += floor($seconds/60.0);
$seconds -= 60*floor($seconds/60.0);
}
if($minutes >= 60)
{
$degrees += floor($minutes/60.0);
$minutes -= 60*floor($minutes/60.0);
}
return array('degrees' => $degrees, 'minutes' => $minutes, 'seconds' => $seconds);
}
function gps2Num($coordPart)
{
$parts = explode('/', $coordPart);
if(count($parts) <= 0)// jic
return 0;
if(count($parts) == 1)
return $parts[0];
return floatval($parts[0]) / floatval($parts[1]);
}
I know this question has been asked a long time ago, but I came across it while searching in google and the solutions proposed here did not worked for me. So, after further searching, here is what worked for me.
I'm putting it here so that anybody who comes here through some googling, can find different approaches to solve the same problem:
function triphoto_getGPS($fileName, $assoc = false)
{
//get the EXIF
$exif = exif_read_data($fileName);
//get the Hemisphere multiplier
$LatM = 1; $LongM = 1;
if($exif["GPSLatitudeRef"] == 'S')
{
$LatM = -1;
}
if($exif["GPSLongitudeRef"] == 'W')
{
$LongM = -1;
}
//get the GPS data
$gps['LatDegree']=$exif["GPSLatitude"][0];
$gps['LatMinute']=$exif["GPSLatitude"][1];
$gps['LatgSeconds']=$exif["GPSLatitude"][2];
$gps['LongDegree']=$exif["GPSLongitude"][0];
$gps['LongMinute']=$exif["GPSLongitude"][1];
$gps['LongSeconds']=$exif["GPSLongitude"][2];
//convert strings to numbers
foreach($gps as $key => $value)
{
$pos = strpos($value, '/');
if($pos !== false)
{
$temp = explode('/',$value);
$gps[$key] = $temp[0] / $temp[1];
}
}
//calculate the decimal degree
$result['latitude'] = $LatM * ($gps['LatDegree'] + ($gps['LatMinute'] / 60) + ($gps['LatgSeconds'] / 3600));
$result['longitude'] = $LongM * ($gps['LongDegree'] + ($gps['LongMinute'] / 60) + ($gps['LongSeconds'] / 3600));
if($assoc)
{
return $result;
}
return json_encode($result);
}
This is an old question but felt it could use a more eloquent solution (OOP approach and lambda to process the fractional parts)
/**
* Example coordinate values
*
* Latitude - 49/1, 4/1, 2881/100, N
* Longitude - 121/1, 58/1, 4768/100, W
*/
protected function _toDecimal($deg, $min, $sec, $ref) {
$float = function($v) {
return (count($v = explode('/', $v)) > 1) ? $v[0] / $v[1] : $v[0];
};
$d = $float($deg) + (($float($min) / 60) + ($float($sec) / 3600));
return ($ref == 'S' || $ref == 'W') ? $d *= -1 : $d;
}
public function getCoordinates() {
$exif = #exif_read_data('image_with_exif_data.jpeg');
$coord = (isset($exif['GPSLatitude'], $exif['GPSLongitude'])) ? implode(',', array(
'latitude' => sprintf('%.6f', $this->_toDecimal($exif['GPSLatitude'][0], $exif['GPSLatitude'][1], $exif['GPSLatitude'][2], $exif['GPSLatitudeRef'])),
'longitude' => sprintf('%.6f', $this->_toDecimal($exif['GPSLongitude'][0], $exif['GPSLongitude'][1], $exif['GPSLongitude'][2], $exif['GPSLongitudeRef']))
)) : null;
}
The code I've used in the past is something like (in reality, it also checks that the data is vaguely valid):
// Latitude
$northing = -1;
if( $gpsblock['GPSLatitudeRef'] && 'N' == $gpsblock['GPSLatitudeRef'] )
{
$northing = 1;
}
$northing *= defraction( $gpsblock['GPSLatitude'][0] ) + ( defraction($gpsblock['GPSLatitude'][1] ) / 60 ) + ( defraction( $gpsblock['GPSLatitude'][2] ) / 3600 );
// Longitude
$easting = -1;
if( $gpsblock['GPSLongitudeRef'] && 'E' == $gpsblock['GPSLongitudeRef'] )
{
$easting = 1;
}
$easting *= defraction( $gpsblock['GPSLongitude'][0] ) + ( defraction( $gpsblock['GPSLongitude'][1] ) / 60 ) + ( defraction( $gpsblock['GPSLongitude'][2] ) / 3600 );
Where you also have:
function defraction( $fraction )
{
list( $nominator, $denominator ) = explode( "/", $fraction );
if( $denominator )
{
return ( $nominator / $denominator );
}
else
{
return $fraction;
}
}
To get the altitude value, you can use the following 3 lines:
$data = exif_read_data($path_to_your_photo, 0, TRUE);
$alt = explode('/', $data["GPS"]["GPSAltitude"]);
$altitude = (isset($alt[1])) ? ($alt[0] / $alt[1]) : $alt[0];
In case you need a function to read Coordinates from Imagick Exif here we go, I hope it saves you time. Tested under PHP 7.
function create_gps_imagick($coordinate, $hemi) {
$exifCoord = explode(', ', $coordinate);
$degrees = count($exifCoord) > 0 ? gps2Num($exifCoord[0]) : 0;
$minutes = count($exifCoord) > 1 ? gps2Num($exifCoord[1]) : 0;
$seconds = count($exifCoord) > 2 ? gps2Num($exifCoord[2]) : 0;
$flip = ($hemi == 'W' or $hemi == 'S') ? -1 : 1;
return $flip * ($degrees + $minutes / 60 + $seconds / 3600);
}
function 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]);
}
I'm using the modified version from Gerald Kaszuba but it's not accurate.
so i change the formula a bit.
from:
return $flip * ($degrees + $minutes / 60);
changed to:
return floatval($flip * ($degrees +($minutes/60)+($seconds/3600)));
It works for me.
This is a javascript port of the PHP-code posted #Gerald above. This way you can figure out the location of an image without ever uploading the image, in conjunction with libraries like dropzone.js and Javascript-Load-Image
define(function(){
function parseExif(map) {
var gps = {
lng : getGps(map.get('GPSLongitude'), data.get('GPSLongitudeRef')),
lat : getGps(map.get('GPSLatitude'), data.get('GPSLatitudeRef'))
}
return gps;
}
function getGps(exifCoord, hemi) {
var degrees = exifCoord.length > 0 ? parseFloat(gps2Num(exifCoord[0])) : 0,
minutes = exifCoord.length > 1 ? parseFloat(gps2Num(exifCoord[1])) : 0,
seconds = exifCoord.length > 2 ? parseFloat(gps2Num(exifCoord[2])) : 0,
flip = (/w|s/i.test(hemi)) ? -1 : 1;
return flip * (degrees + (minutes / 60) + (seconds / 3600));
}
function gps2Num(coordPart) {
var parts = (""+coordPart).split('/');
if (parts.length <= 0) {
return 0;
}
if (parts.length === 1) {
return parts[0];
}
return parts[0] / parts[1];
}
return {
parseExif: parseExif
};
});
short story.
First part N
Leave the grade
multiply the minutes with 60
devide the seconds with 100.
count the grades,minuts and seconds with eachother.
Second part E
Leave the grade
multiply the minutes with 60
devide the seconds with ...1000
cöunt the grades, minutes and seconds with each other
i have seen nobody mentioned this: https://pypi.python.org/pypi/LatLon/1.0.2
from fractions import Fraction
from LatLon import LatLon, Longitude, Latitude
latSigned = GPS.GPSLatitudeRef == "N" ? 1 : -1
longSigned = GPS.GPSLongitudeRef == "E" ? 1 : -1
latitudeObj = Latitude(
degree = float(Fraction(GPS.GPSLatitude[0]))*latSigned ,
minute = float(Fraction(GPS.GPSLatitude[0]))*latSigned ,
second = float(Fraction(GPS.GPSLatitude[0])*latSigned)
longitudeObj = Latitude(
degree = float(Fraction(GPS.GPSLongitude[0]))*longSigned ,
minute = float(Fraction(GPS.GPSLongitude[0]))*longSigned ,
second = float(Fraction(GPS.GPSLongitude[0])*longSigned )
Coordonates = LatLon(latitudeObj, longitudeObj )
now using the Coordonates objecct you can do what you want:
Example:
(like 46°56′48″N 7°26′39″E from wikipedia)
print Coordonates.to_string('d%°%m%′%S%″%H')
You than have to convert from ascii, and you are done:
('5\xc2\xb052\xe2\x80\xb259.88\xe2\x80\xb3N', '162\xc2\xb04\xe2\x80\xb259.88\xe2\x80\xb3W')
and than printing example:
print "Latitude:" + Latitude.to_string('d%°%m%′%S%″%H')[0].decode('utf8')
>> Latitude: 5°52′59.88″N
Related
I want to convert a number into a string representation with a format similar to Stack Overflow reputation display.
e.g.
999 == '999'
1000 == '1,000'
9999 == '9,999'
10000 == '10k'
10100 == '10.1k'
Another approach that produces exactly the desired output:
function getRepString (rep) {
rep = rep+''; // coerce to string
if (rep < 1000) {
return rep; // return the same number
}
if (rep < 10000) { // place a comma between
return rep.charAt(0) + ',' + rep.substring(1);
}
// divide and format
return (rep/1000).toFixed(rep % 1000 != 0)+'k';
}
Check the output results here.
UPDATE:
CMS got the check and provides a superior answer. Send any more votes his way.
// formats a number similar to the way stack exchange sites
// format reputation. e.g.
// for numbers< 10000 the output is '9,999'
// for numbers > 10000 the output is '10k' with one decimal place when needed
function getRepString(rep)
{
var repString;
if (rep < 1000)
{
repString = rep;
}
else if (rep < 10000)
{
// removed my rube goldberg contraption and lifted
// CMS version of this segment
repString = rep.charAt(0) + ',' + rep.substring(1);
}
else
{
repString = (Math.round((rep / 1000) * 10) / 10) + "k"
}
return repString.toString();
}
Output:
getRepString(999) == '999'
getRepString(1000) == '1,000'
getRepString(9999) == '9,999'
getRepString(10000) == '10k'
getRepString(10100) == '10.1k'
Here is a function in PHP which is part of iZend - http://www.izend.org/en/manual/library/countformat:
function count_format($n, $point='.', $sep=',') {
if ($n < 0) {
return 0;
}
if ($n < 10000) {
return number_format($n, 0, $point, $sep);
}
$d = $n < 1000000 ? 1000 : 1000000;
$f = round($n / $d, 1);
return number_format($f, $f - intval($f) ? 1 : 0, $point, $sep) . ($d == 1000 ? 'k' : 'M');
}
Here is CMS's version in PHP (in case someone needed it, like I did):
function getRepString($rep) {
$rep = intval($rep);
if ($rep < 1000) {
return (string)$rep;
}
if ($rep < 10000) {
return number_format($rep);
}
return number_format(($rep / 1000), ($rep % 1000 != 0)) . 'k';
}
// TEST
var_dump(getRepString(999));
var_dump(getRepString(1000));
var_dump(getRepString(9999));
var_dump(getRepString(10000));
var_dump(getRepString(10100));
Output:
string(3) "999"
string(5) "1,000"
string(5) "9,999"
string(3) "10k"
string(5) "10.1k"
Handlebars.registerHelper("classNameHere",function(rep) {
var repString;
if (rep < 1000)
{
repString = rep;
}
else if (rep < 10000)
{
rep = String(rep);
r = rep.charAt(0);
s = rep.substring(1);
repString = r + ',' + s;
}
else
{
repDecimal = Math.round(rep / 100) / 10;
repString = repDecimal + "k";
}
return repString.toString();
});
divide by 1000 then if result is greater than 1 round the number and concantenate a "k" on the end.
If the result is less than 1 just output the actual result!
// Shortens a number and attaches K, M, B, etc. accordingly
function number_shorten($number, $precision = 3, $divisors = null) {
// Setup default $divisors if not provided
if (!isset($divisors)) {
$divisors = array(
pow(1000, 0) => '', // 1000^0 == 1
pow(1000, 1) => 'K', // Thousand
pow(1000, 2) => 'M', // Million
pow(1000, 3) => 'B', // Billion
pow(1000, 4) => 'T', // Trillion
pow(1000, 5) => 'Qa', // Quadrillion
pow(1000, 6) => 'Qi', // Quintillion
);
}
// Loop through each $divisor and find the
// lowest amount that matches
foreach ($divisors as $divisor => $shorthand) {
if (abs($number) < ($divisor * 1000)) {
// We found a match!
break;
}
}
// We found our match, or there were no matches.
// Either way, use the last defined value for $divisor.
return number_format($number / $divisor, $precision) . $shorthand;
}
This worked for me. I hope, this will help you. Thanks for asking this question.
I created an npm (and bower) module to do this:
npm install --save approximate-number
Usage:
var approx = require('approximate-number');
approx(123456); // "123k"
I'm using MKMapView and I send my php program the visible region (center lat, center lon, span lat, span lon). I need to determine if a coordinate is inside that region using php. I'm hoping there's a standard formula somewhere, but I haven't found one. I'll keep trying to come up with a formula, but it's surprisingly complicated (hopefully not as much as the haversine, which I don't believe I could have figured out myself).
lets try this logic
$topRightLongitude = $centerLongitude + $spanLongitude/2;
if($topRightLongitude > 180 and ($pointLongitude < 0))
$topRightLongitude = $topRightLongitude - 360; // (180*2) - positive becomes negative
$bottomLeftLongitude = $centerLongitude - $spanLongitude/2;
if($bottomLeftLongitude< -180 and ($pointLongitude > 0))
$bottomLeftLongitude= 360 + $bottomLeftLongitude; // now is negative and will become positive
$topRightLatitude = $centerLatitude + $spanLatitude/2;
if($topRightLatitude > 90 and ($pointLatitude < 0))
$topRightLatitude = $topRightLatitude - 180; // (90*2) - positive becomes negative
$bottomLeftLatitude = $centerLatitude - $spanLatitude/2;
if($bottomLeftLatitude< -90 and ($pointLatitude > 0))
$bottomLeftLatitude= 180 + $bottomLeftLongitude; // now is negative and will become positive
if you have
$centerLongitude = 179;
$spanLongitude = 20;
$pointLongitude = -179;
results
$topRightLongitude = -171;
$bottomLeftLongitude = 169;
so your point is in if you test like this:
if($pointLongitude < $topRightLongitude &&
$pointLongitude > $bottomLeftLongitude &&
$pointLatitude < $topRightLatitude &&
$pointLatitude > $bottomLeftLatitude){
echo 'in';
}else{
echo 'out';
}
My Solution
$top = $c_lat + ($d_lat / 2.0);
$bottom = $c_lat - ($d_lat / 2.0);
$left = $c_lon - ($d_lon / 2.0);
$right = $c_lon + ($d_lon / 2.0);
if($left < -180)
{
$second_left = $left + 360.0;
$second_right = 180.0;
$left = -180;
}
elseif($right > 180)
{
$second_right = $right - 360.0;
$second_left = -180.0;
$right = 180.0;
}
$inside = false;
if($t_lat > $bottom && $t_lat < $top && $t_lon > $left && $t_lon < $right)
$inside = true;
else if($second_right && $second_left)
{
if($t_lat > $bottom && $t_lat < $top && $t_lon > $second_left && $t_lon < $second_right)
$inside = true;
}
if($inside)
{
}
This seems to work with MKMapView since the region latitudes are always between -90 and 90.
This logic should work:
if ( ($X > $center_lat - $span_lat/2) &&
($X < $center_lat + $span_lat/2) &&
($Y > $center_lon - $span_lon/2) &&
($Y < $center_lon + $span_lon/2) ) {
echo "It's inside!";
} else {
echo "It's outside ...";
}
I had worked a solution for my own problem before, but for decimal values of coordinates and it works. May be if you can convert deg to decimal it might work.
I have renamed the variable according to your problem.
Here's the logic.
if
(
(
($lat - $spanLat) < $centerLat &&
$centerLat < ($lat+ $spanLat)
) &&
(
($long - $spanLong) < $centerLong &&
$centerLong < ($long + $spanLong)
)
)
Currently, I'm learning to use Google Maps API. From what I read, the API require the latitude and longitude in Decimal Degree (DD).
In my database, the data is stored as DMS.
Example, 110° 29' 01.1"
I would to ask if you guys have any DMS to DD in php. And, the converter must accept from a single string like the example above.
Regards
You can try if this is working for you.
<?php
function DMStoDD($deg,$min,$sec)
{
// Converting DMS ( Degrees / minutes / seconds ) to decimal format
return $deg+((($min*60)+($sec))/3600);
}
function DDtoDMS($dec)
{
// Converts decimal format to DMS ( Degrees / minutes / seconds )
$vars = explode(".",$dec);
$deg = $vars[0];
$tempma = "0.".$vars[1];
$tempma = $tempma * 3600;
$min = floor($tempma / 60);
$sec = $tempma - ($min*60);
return array("deg"=>$deg,"min"=>$min,"sec"=>$sec);
}
?>
Here's one where you pass in the latitude,longitude in DMS values and returns the converted DMS string. Easy and simnple
function DECtoDMS($latitude, $longitude)
{
$latitudeDirection = $latitude < 0 ? 'S': 'N';
$longitudeDirection = $longitude < 0 ? 'W': 'E';
$latitudeNotation = $latitude < 0 ? '-': '';
$longitudeNotation = $longitude < 0 ? '-': '';
$latitudeInDegrees = floor(abs($latitude));
$longitudeInDegrees = floor(abs($longitude));
$latitudeDecimal = abs($latitude)-$latitudeInDegrees;
$longitudeDecimal = abs($longitude)-$longitudeInDegrees;
$_precision = 3;
$latitudeMinutes = round($latitudeDecimal*60,$_precision);
$longitudeMinutes = round($longitudeDecimal*60,$_precision);
return sprintf('%s%s° %s %s %s%s° %s %s',
$latitudeNotation,
$latitudeInDegrees,
$latitudeMinutes,
$latitudeDirection,
$longitudeNotation,
$longitudeInDegrees,
$longitudeMinutes,
$longitudeDirection
);
}
I wrote a PHP function that does what the question asks: converts a string in degrees/minutes/seconds into decimal degrees. It accepts a number of different formats for the string, and honors direction (NSEW).
Here is the code:
<?php
function convertDMSToDecimal($latlng) {
$valid = false;
$decimal_degrees = 0;
$degrees = 0; $minutes = 0; $seconds = 0; $direction = 1;
// Determine if there are extra periods in the input string
$num_periods = substr_count($latlng, '.');
if ($num_periods > 1) {
$temp = preg_replace('/\./', ' ', $latlng, $num_periods - 1); // replace all but last period with delimiter
$temp = trim(preg_replace('/[a-zA-Z]/','',$temp)); // when counting chunks we only want numbers
$chunk_count = count(explode(" ",$temp));
if ($chunk_count > 2) {
$latlng = $temp; // remove last period
} else {
$latlng = str_replace("."," ",$latlng); // remove all periods, not enough chunks left by keeping last one
}
}
// Remove unneeded characters
$latlng = trim($latlng);
$latlng = str_replace("º","",$latlng);
$latlng = str_replace("'","",$latlng);
$latlng = str_replace("\"","",$latlng);
$latlng = substr($latlng,0,1) . str_replace('-', ' ', substr($latlng,1)); // remove all but first dash
if ($latlng != "") {
// DMS with the direction at the start of the string
if (preg_match("/^([nsewNSEW]?)\s*(\d{1,3})\s+(\d{1,3})\s+(\d+\.?\d*)$/",$latlng,$matches)) {
$valid = true;
$degrees = intval($matches[2]);
$minutes = intval($matches[3]);
$seconds = floatval($matches[4]);
if (strtoupper($matches[1]) == "S" || strtoupper($matches[1]) == "W")
$direction = -1;
}
// DMS with the direction at the end of the string
if (preg_match("/^(-?\d{1,3})\s+(\d{1,3})\s+(\d+(?:\.\d+)?)\s*([nsewNSEW]?)$/",$latlng,$matches)) {
$valid = true;
$degrees = intval($matches[1]);
$minutes = intval($matches[2]);
$seconds = floatval($matches[3]);
if (strtoupper($matches[4]) == "S" || strtoupper($matches[4]) == "W" || $degrees < 0) {
$direction = -1;
$degrees = abs($degrees);
}
}
if ($valid) {
// A match was found, do the calculation
$decimal_degrees = ($degrees + ($minutes / 60) + ($seconds / 3600)) * $direction;
} else {
// Decimal degrees with a direction at the start of the string
if (preg_match("/^(-?\d+(?:\.\d+)?)\s*([nsewNSEW]?)$/",$latlng,$matches)) {
$valid = true;
if (strtoupper($matches[2]) == "S" || strtoupper($matches[2]) == "W" || $degrees < 0) {
$direction = -1;
$degrees = abs($degrees);
}
$decimal_degrees = $matches[1] * $direction;
}
// Decimal degrees with a direction at the end of the string
if (preg_match("/^([nsewNSEW]?)\s*(\d+(?:\.\d+)?)$/",$latlng,$matches)) {
$valid = true;
if (strtoupper($matches[1]) == "S" || strtoupper($matches[1]) == "W")
$direction = -1;
$decimal_degrees = $matches[2] * $direction;
}
}
}
if ($valid) {
return $decimal_degrees;
} else {
return false;
}
}
?>
Here it is on Github with test cases: https://github.com/prairiewest/PHPconvertDMSToDecimal
Solved.
<?php
function DMStoDD($input)
{
$deg = " " ;
$min = " " ;
$sec = " " ;
$inputM = " " ;
print "<br> Input is ".$input." <br>";
for ($i=0; $i < strlen($input); $i++)
{
$tempD = $input[$i];
//print "<br> TempD [$i] is : $tempD";
if ($tempD == iconv("UTF-8", "ISO-8859-1//TRANSLIT", '°') )
{
$newI = $i + 1 ;
//print "<br> newI is : $newI";
$inputM = substr($input, $newI, -1) ;
break;
}//close if degree
$deg .= $tempD ;
}//close for degree
//print "InputM is ".$inputM." <br>";
for ($j=0; $j < strlen($inputM); $j++)
{
$tempM = $inputM[$j];
//print "<br> TempM [$j] is : $tempM";
if ($tempM == "'")
{
$newI = $j + 1 ;
//print "<br> newI is : $newI";
$sec = substr($inputM, $newI, -1) ;
break;
}//close if minute
$min .= $tempM ;
}//close for min
$result = $deg+( (( $min*60)+($sec) ) /3600 );
print "<br> Degree is ". $deg*1 ;
print "<br> Minutes is ". $min ;
print "<br> Seconds is ". $sec ;
print "<br> Result is ". $result ;
return $deg + ($min / 60) + ($sec / 3600);
}
?>
If you also want to include the reference, you might want to use this function:
function DMStoDD($ref, $deg, $min, $sec)
{
$n = $deg + (($min * 60 + $sec) / 3600);
if (($ref == "S") or ($ref == "W")) {
return -$n;
} else {
return $n;
}
}
This works very well:
<?php echo "<td> $deg° $min' $sec″ </td>"; ?>
where deg, min and sec are the angular co-ordinates.
I have multiple JavaScript functions to calculate mayan horoscope sign, which I converted to a single PHP class. Everything works well, except some dates. For example, the date 06.10.1977 gives me a NULL result using the PHP class, but this date in the equivalent JavaScript function returns a proper value. Maybe I've got something wrong in PHP so, can somebody check this up for me?
JAVASCRIPT:
function leap_gregorian(year) {
return ((year % 4) == 0) && (!(((year % 100) == 0) && ((year % 400) != 0)));
}
// GREGORIAN_TO_JD -- Determine Julian day number from Gregorian calendar date
var GREGORIAN_EPOCH = 1721425.5;
function gregorian_to_jd(year, month, day) {
return (GREGORIAN_EPOCH - 1) +
(365 * (year - 1)) +
Math.floor((year - 1) / 4) +
(-Math.floor((year - 1) / 100)) +
Math.floor((year - 1) / 400) +
Math.floor((((367 * month) - 362) / 12) +
((month <= 2) ? 0 : (leap_gregorian(year) ? -1 : -2)) +
day);
}
var MAYAN_COUNT_EPOCH = 584282.5;
// JD_TO_MAYAN_COUNT -- Calculate Mayan long count from Julian day
function jd_to_mayan_count(jd) {
var d, baktun, katun, tun, uinal, kin;
jd = Math.floor(jd) + 0.5;
d = jd - MAYAN_COUNT_EPOCH;
baktun = Math.floor(d / 144000);
d = mod(d, 144000);
katun = Math.floor(d / 7200);
d = mod(d, 7200);
tun = Math.floor(d / 360);
d = mod(d, 360);
uinal = Math.floor(d / 20);
kin = mod(d, 20);
return new Array(baktun, katun, tun, uinal, kin);
}
// JD_TO_MAYAN_HAAB -- Determine Mayan Haab "month" and day from Julian day
var MAYAN_HAAB_MONTHS = new Array("Pop", "Uo", "Zip", "Zotz", "Tzec", "Xul",
"Yaxkin", "Mol", "Chen", "Yax", "Zac", "Ceh",
"Mac", "Kankin", "Muan", "Pax", "Kayab", "Cumku", "Uayeb");
function jd_to_mayan_haab(jd) {
var lcount, day;
jd = Math.floor(jd) + 0.5;
lcount = jd - MAYAN_COUNT_EPOCH;
day = mod(lcount + 8 + ((18 - 1) * 20), 365);
return new Array(Math.floor(day / 20) + 1, mod(day, 20));
}
// JD_TO_MAYAN_TZOLKIN -- Determine Mayan Tzolkin "month" and day from Julian day
var MAYAN_TZOLKIN_MONTHS = new Array("Imix", "Ik", "Akbal", "Kan", "Chicchan",
"Cimi", "Manik", "Lamat", "Muluc", "Oc",
"Chuen", "Eb", "Ben", "Ix", "Men",
"Cib", "Caban", "Etznab", "Cauac", "Ahau");
var MAYAN_TZOLKIN_MONTHS_EN = new Array("Crocodile", "Wind", "House", "Lizard", "Serpent",
"Death", "Deer", "Rabbit", "Water", "Dog",
"Monkey", "Grass", "Reed", "Jaguar", "Eagle",
"Vulture", "Earth", "Knife", "Storm", "Flower");
function jd_to_mayan_tzolkin(jd) {
var lcount;
jd = Math.floor(jd) + 0.5;
lcount = jd - MAYAN_COUNT_EPOCH;
return new Array(amod(lcount + 20, 20), amod(lcount + 4, 13));
}
function getMayanSign(sign, month, day, year) {
var isValidated = true;
var result = "";
if (!IsNumeric(month.value) || month.value <= 0 || month.value > 12) {
month.value = "MM";
isValidated = false;
}
if (!IsNumeric(day.value) || day.value <= 0 || day.value > 31) {
day.value = "DD";
isValidated = false;
}
if (!IsNumeric(year.value) || year.value < 1900 ) {
year.value = "YYYY";
isValidated = false;
}
if (isValidated) {
year = new Number(year.value);
mon = new Number(month.value);
mday = new Number(day.value);
hour = min = sec = 0;
// Update Julian day
j = gregorian_to_jd(year, mon + 0, mday) +
(Math.floor(sec + 60 * (min + 60 * hour) + 0.5) / 86400.0);
maytzolkincal = jd_to_mayan_tzolkin(j);
result = MAYAN_TZOLKIN_MONTHS_EN[maytzolkincal[0] - 1];
} else result = "INVALID BIRTHDAY";
sign.value = result;
}
PHP CLASS:
class MayanCalculator {
// JD_TO_MAYAN_TZOLKIN -- Determine Mayan Tzolkin "month" and day from Julian day
private $MAYAN_TZOLKIN_MONTHS = array("Imix", "Ik", "Akbal", "Kan", "Chicchan",
"Cimi", "Manik", "Lamat", "Muluc", "Oc",
"Chuen", "Eb", "Ben", "Ix", "Men",
"Cib", "Caban", "Etznab", "Cauac", "Ahau");
private $MAYAN_TZOLKIN_MONTHS_EN = array("Crocodile", "Wind", "House", "Lizard", "Serpent",
"Death", "Deer", "Rabbit", "Water", "Dog",
"Monkey", "Grass", "Reed", "Jaguar", "Eagle",
"Vulture", "Earth", "Knife", "Storm", "Flower");
private $GREGORIAN_EPOCH = 1721425.5;
private $MAYAN_COUNT_EPOCH = 584282.5;
private $MAYAN_HAAB_MONTHS = array("Pop", "Uo", "Zip", "Zotz", "Tzec", "Xul",
"Yaxkin", "Mol", "Chen", "Yax", "Zac", "Ceh",
"Mac", "Kankin", "Muan", "Pax", "Kayab", "Cumku", "Uayeb");
private $_day;
private $_month;
private $_year;
private $sign;
private $signm;
function __construct($day, $month, $year) {
$this->_day = $day;
$this->_month = $month;
$this->_year = $year;
}
public function getMayanSign() {
$this->sign = $this->getSign();
$this->signm = $this->getMayanSignOnMayan();
$ids = new getIDS(null,null,$this->sign);
$id = $ids->returnIDS();
return array("mayan_sign" => $this->sign, "mayan_sign_on_mayan" => $this->signm, "mayan_sign_id" => $id["mayan_id"]);
}
private function getSign() {
$isValidated = true;
$result = null;
if (!is_numeric($this->_month) || $this->_month <= 0 || $this->_month > 12) :
$isValidated = false;
endif;
if (!is_numeric($this->_day) || $this->_day <= 0 || $this->_day > 31) :
$isValidated = false;
endif;
if (!is_numeric($this->_year) || $this->_year < 1900) :
$isValidated = false;
endif;
if ($isValidated) :
$hour = 0;
$min = 0;
$sec = 0;
//update julian day
$j = $this->gregorian_to_jd($this->_year, $this->_month+0, $this->_day) + (floor($sec + 60 * ($min + 60 * $hour) + 0.5) / 86400.0);
$maytzolkincal = $this->jd_to_mayan_tzolkin($j);
$result = $this->MAYAN_TZOLKIN_MONTHS_EN[$maytzolkincal[0]-1];
else :
$result = 'Wrong date '. $this->_day . '.' . $this->_month . '.' . $this->_year;
endif;
return $result;
}
private function getMayanSignOnMayan() {
$isValidated = true;
$result = null;
if (!is_numeric($this->_month) || $this->_month <= 0 || $this->_month > 12) :
$isValidated = false;
endif;
if (!is_numeric($this->_day) || $this->_day <= 0 || $this->_day > 31) :
$isValidated = false;
endif;
if (!is_numeric($this->_year) || $this->_year < 1900) :
$isValidated = false;
endif;
if ($isValidated) :
$hour = 0;
$min = 0;
$sec = 0;
//update julian day
$j = $this->gregorian_to_jd($this->_year, $this->_month+0, $this->_day) + (floor($sec + 60 * ($min + 60 * $hour) + 0.5) / 86400);
$maytzolkincal = $this->jd_to_mayan_tzolkin($j);
$result = $this->MAYAN_TZOLKIN_MONTHS[$maytzolkincal[0]]-1;
else :
$result = 'Wrong date '. $this->_day . '.' . $this->_month . '.' . $this->_year;
endif;
return $result;
}
private function leap_gregorian($year) {
return (($year % 4) == 0) && (!((($year % 100) == 0) && (($year % 400) != 0)));
}
private function gregorian_to_jd($year, $month, $day) {
$result = ($this->GREGORIAN_EPOCH - 1) +
(365 * ($year - 1)) +
floor(($year - 1) / 4) +
(-floor(($year - 1) / 100)) +
floor(($year - 1) / 400) +
floor((((367 * $month) - 362) / 12) +
(($month <= 2) ? 0 : ($this->leap_gregorian($year) ? -1 : -2)) +
$day);
return $result;
}
private function jd_to_mayan_count($jd) {
$jd = floor($jd) + 0.5;
$d = $jd - $this->MAYAN_COUNT_EPOCH;
$baktun = floor($d / 144000);
$d = $d % 144000;
$katun = floor($d / 7200);
$d = $d % 7200;
$tun = floor($d / 360);
$d = $d % 360;
$uinal = floor($d / 20);
$kin = $d % 20;
return array($baktun, $katun, $tun, $uinal, $kin);
}
private function jd_to_mayan_haab($jd) {
$jd = floor($jd) + 0.5;
$lcount = $jd - $this->MAYAN_COUNT_EPOCH;
$day = ($lcount + 8 + ((18 - 1) * 20)) % 365;
return array(floor($day / 20) + 1, $day % 20);
}
private function jd_to_mayan_tzolkin($jd) {
$jd = floor($jd) + 0.5;
$lcount = $jd - $this->MAYAN_COUNT_EPOCH;
return array(($lcount + 20) % 20, ($lcount + 4) % 13);
}
}
First: Always post all relevant script. Yours was missing the JavaScript function amod() which is crucial to solving your problem, I found it in the original here
Your PHP function jd_to_mayan_tzolkin() ist different from your JavaScript function jd_to_mayan_tzolkin():
While JavaScript uses a separate function amod() to calculate the modulus and returns the numerator if the modulus-result is 0
// AMOD -- Modulus function which returns numerator if modulus is zero
function amod($a, $b) {
return $this->mod($a - 1, $b) + 1;
}
your php function just returns the modulus which can be 0 and therefore the further processing of your PHP fails.
Add the following two functions to your class:
/* MOD -- Modulus function which works for non-integers. */
private function mod($a, $b) {
return $a - ($b * floor($a / $b));
}
// AMOD -- Modulus function which returns numerator if modulus is zero
private function amod($a, $b) {
return $this->mod($a - 1, $b) + 1;
}
And change the PHP method jd_to_mayan_tzolkin() as follows:
private function jd_to_mayan_tzolkin($jd) {
$jd = floor($jd) + 0.5;
$lcount = $jd - $this->MAYAN_COUNT_EPOCH;
return array($this->amod(($lcount + 20),20), $this->amod(($lcount + 4),13));
}
How can I convert this:
26.72773551940918
Into something like this:
22°12'42"N
The trick here is that the coordinates are, actually Latitude and Longitude, I just need to format them correctly.
You can find functions to do that here
<?php
function DMStoDEC($deg,$min,$sec)
{
// Converts DMS ( Degrees / minutes / seconds )
// to decimal format longitude / latitude
return $deg+((($min*60)+($sec))/3600);
}
function DECtoDMS($dec)
{
// Converts decimal longitude / latitude to DMS
// ( Degrees / minutes / seconds )
// This is the piece of code which may appear to
// be inefficient, but to avoid issues with floating
// point math we extract the integer part and the float
// part by using a string function.
$vars = explode(".",$dec);
$deg = $vars[0];
$tempma = "0.".$vars[1];
$tempma = $tempma * 3600;
$min = floor($tempma / 60);
$sec = $tempma - ($min*60);
return array("deg"=>$deg,"min"=>$min,"sec"=>$sec);
}
?>
The lat/lon coords are written in (roughly speaking) a base-60 numeral system. Here's how you convert them:
function fraction_to_min_sec($coord)
{
$isnorth = $coord>=0;
$coord = abs($coord);
$deg = floor($coord);
$coord = ($coord-$deg)*60;
$min = floor($coord);
$sec = floor(($coord-$min)*60);
return array($deg, $min, $sec, $isnorth ? 'N' : 'S');
// or if you want the string representation
return sprintf("%d°%d'%d\"%s", $deg, $min, $sec, $isnorth ? 'N' : 'S');
}
I say my function has better numerical stability than #SeRPRo's one.
Here's one where you pass in the latitude,longitude in DMS values and returns the converted DMS string. Easy and simple
function DECtoDMS($latitude, $longitude)
{
$latitudeDirection = $latitude < 0 ? 'S': 'N';
$longitudeDirection = $longitude < 0 ? 'W': 'E';
$latitudeNotation = $latitude < 0 ? '-': '';
$longitudeNotation = $longitude < 0 ? '-': '';
$latitudeInDegrees = floor(abs($latitude));
$longitudeInDegrees = floor(abs($longitude));
$latitudeDecimal = abs($latitude)-$latitudeInDegrees;
$longitudeDecimal = abs($longitude)-$longitudeInDegrees;
$_precision = 3;
$latitudeMinutes = round($latitudeDecimal*60,$_precision);
$longitudeMinutes = round($longitudeDecimal*60,$_precision);
return sprintf('%s%s° %s %s %s%s° %s %s',
$latitudeNotation,
$latitudeInDegrees,
$latitudeMinutes,
$latitudeDirection,
$longitudeNotation,
$longitudeInDegrees,
$longitudeMinutes,
$longitudeDirection
);
}
Here is the opposite when you have DMS string and need it as float number (contains unicode characters):
//e.g.
$dec = dms_to_dec("-18° 51' 30.5697\"");
/**
* Convert a coordinate in dms to dec
*
* #param string $dms coordinate
* #return float
*/
function dms_to_dec($dms)
{
$dms = stripslashes($dms);
$neg = (preg_match('/[SWO]/i', $dms) == 0) ? 1 : -1;
$dms = preg_replace('/(^\s?-)|(\s?[NSEWO]\s?)/i', '', $dms);
$pattern = "/(\\d*\\.?\\d+)(?:[°ºd: ]+)(\\d*\\.?\\d+)*(?:['m′: ])*(\\d*\\.?\\d+)*[\"s″ ]?/i";
$parts = preg_split($pattern, $dms, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
if (!$parts) {
return;
}
// parts: 0 = degree, 1 = minutes, 2 = seconds
$d = isset($parts[0]) ? (float)$parts[0] : 0;
$m = isset($parts[1]) ? (float)$parts[1] : 0;
if (strpos($dms, ".") > 1 && isset($parts[2])) {
$m = (float)($parts[1] . '.' . $parts[2]);
unset($parts[2]);
}
$s = isset($parts[2]) ? (float)$parts[2] : 0;
$dec = ($d + ($m/60) + ($s/3600))*$neg;
return $dec;
}