This question already has answers here:
How to get a time zone from a location using latitude and longitude coordinates?
(18 answers)
Closed 6 years ago.
Is there a way get the timezone of a user by their latitude and longitude? And not just the offset, but the actual timezone they're in.
Essentially, I'm searching for the polar opposite of DateTimeZone::getLocation which returns the latitude and longitude for a certain timezone.
For those who wants to get timezone from country code, latitude and longitude. ( easy to get it if you have a geoip module installed on your server )
Try this, I've added a distance calculation - only for those countries which has multiple timezones.
Ah, and the country code is a two letter ISO code.
// ben#jp
function get_nearest_timezone($cur_lat, $cur_long, $country_code = '') {
$timezone_ids = ($country_code) ? DateTimeZone::listIdentifiers(DateTimeZone::PER_COUNTRY, $country_code)
: DateTimeZone::listIdentifiers();
if($timezone_ids && is_array($timezone_ids) && isset($timezone_ids[0])) {
$time_zone = '';
$tz_distance = 0;
//only one identifier?
if (count($timezone_ids) == 1) {
$time_zone = $timezone_ids[0];
} else {
foreach($timezone_ids as $timezone_id) {
$timezone = new DateTimeZone($timezone_id);
$location = $timezone->getLocation();
$tz_lat = $location['latitude'];
$tz_long = $location['longitude'];
$theta = $cur_long - $tz_long;
$distance = (sin(deg2rad($cur_lat)) * sin(deg2rad($tz_lat)))
+ (cos(deg2rad($cur_lat)) * cos(deg2rad($tz_lat)) * cos(deg2rad($theta)));
$distance = acos($distance);
$distance = abs(rad2deg($distance));
// echo '<br />'.$timezone_id.' '.$distance;
if (!$time_zone || $tz_distance > $distance) {
$time_zone = $timezone_id;
$tz_distance = $distance;
}
}
}
return $time_zone;
}
return 'unknown';
}
//timezone for one NY co-ordinate
echo get_nearest_timezone(40.772222,-74.164581) ;
// more faster and accurate if you can pass the country code
echo get_nearest_timezone(40.772222, -74.164581, 'US') ;
Geonames should do the job nicely:
http://www.geonames.org/
They've also got a php library.
A good resource is the Google Time Zone API.
Documentation: https://developers.google.com/maps/documentation/timezone/
It takes latitude and longitude and returns array like this:
array(
'dstOffset' => (int) 3600,
'rawOffset' => (int) -18000,
'status' => 'OK',
'timeZoneId' => 'America/New_York',
'timeZoneName' => 'Eastern Daylight Time'
)
...but there are some limits:
[updated 2019] The Google Time Zone API has usage limits in place. Basically, billing must be enabled on your project, but a $200 USD "Google Maps Platform credit" is applied each month (so in most cases your first 40,000 Time Zone API calls/month would be free of charge).
Full details here and here.
I did a timezone solution recently in an 8 hour long hackathon. It's quickly put together and I'd love to develop it further and sell it as a product but since there is no way for me to do it, I've open sourced it at my github.
There is a demo too but it may go down if it hits resource limits. It's a free webapp on Google App Engine.
You can definitely optimize/augment this further in wrt - running time, space, data - to suit your needs.
The Yahoo places API provides timezone information via reverse geolocation.
Check it out.
http://developer.yahoo.com/geo/placefinder/guide/requests.html
How about finding the closest point to the one in the list of all timezone locations? I wonder how accurate is this?
UPDATE:
Eventually, I came up with this snippet that works for me. This will work fine for all locations, but may not be accurate for those close to borders.
/**
* Attempts to find the closest timezone by coordinates
*
* #static
* #param $lat
* #param $lng
*/
public static function getClosestTimezone($lat, $lng)
{
$diffs = array();
foreach(DateTimeZone::listIdentifiers() as $timezoneID) {
$timezone = new DateTimeZone($timezoneID);
$location = $timezone->getLocation();
$tLat = $location['latitude'];
$tLng = $location['longitude'];
$diffLat = abs($lat - $tLat);
$diffLng = abs($lng - $tLng);
$diff = $diffLat + $diffLng;
$diffs[$timezoneID] = $diff;
}
//asort($diffs);
$timezone = array_keys($diffs, min($diffs));
return $timezone[0];
}
Related
Problem
I was in need of a search by range of distance from latitude and longitude. This was relatively straight forward in SQL, however, in couchbase I came across some difficulty. I eventually discovered that spatial views offer a solution for this, however the spatial views aren't accessible via the PHP SDK (yet) and so you had to use the REST Api as an alternative.
Solution
I can get a result set within a range from a latitude and longitude using this method, but now need to sort by date created. Please see end of this post.
My documents are as such:
{
"docType" : "post",
"title" : "Test post",
"location" : {
"latitude" : 1.123456789
"longitude" : -13.9876543210
}
"created" : 1395329441
}
The trick is to create an index with a normal view which will format the keys in a specific way.
The formula
Preppend all keys with "point::"
Add a positive or negative sign "+", or "-"
Make the integer part 3 characters long by prepending with "0"s
Make the decimal part 9 characters long by appending with "0"
Repeat for both latitude and longitude
Example of a Key produced from the document above:
"point::+001.123456789,-013.987654321"
This will give you a key formatted in a way that will be sortable by the unicode collation which couchbase use.
The View
function (doc, meta) {
if(meta.type == "json" && doc.docType == "post" && doc.location){
if(doc.location.latitude && doc.location.longitude){
var prefix = "point::";
var latitude = doc.location.latitude.toString();
var longitude = doc.location.longitude.toString();
var pointKey = prefix.concat(buildKey(latitude), ",", buildKey(longitude));
emit(pointKey, meta.id);
function buildKey(coord){
// positive or negative sign
var sign;
if(coord.substring(0,1) == "-"){
sign = "-";
coord = coord.substring(1, coord.length);
} else {
sign = "+";
}
// remove "+" (incase it is there), though normall this isnt expressed
if(coord.substring(0,1) == "+"){
coord = coord.substring(1, coord.length);
}
var intSize = "000";
var decSize = "000000000";
// get integer and decimal parts of latitude
var parts = coord.split(".");
var int = parts[0];
var dec = parts[1];
// prepend int with 0's so it has a length of 3
if(int.length < intSize.length){
int = intSize.substring(0, intSize.length - int.length) + int;
}
int = int.substring(0,3);
// append dec with 0's so it has a length of 9
if(dec.length < decSize.length){
dec = dec + decSize.substring(0, decSize.length - dec.length);
}
dec = dec.substring(0,9);
return sign.concat(int, ".", dec);
}
}
}
}
The PHP
Now in PHP convert your latitude and longitude into a key in the same format. You can even work out a key for minimum latitude and minimum longitude and maximum latitude and maximum longitude so you can do a search by a "range" (Range will be a box not a circle, but good enough to narrow result set significantly)
For example:
$location = GeoLocation::fromDegrees($data['location']['latitude'], $data['location']['longitude']);
$coordinates = $location->boundingCoordinates(50, 'miles');
$startkey = "point::" . $location->buildKey($coordinates[0]->getLatitudeInDegrees()) . "," . $location->buildKey($coordinates[0]->getLongitudeInDegrees());
$endkey = "point::" . $location->buildKey($coordinates[1]->getLatitudeInDegrees()) . "," . $location->buildKey($coordinates[1]->getLongitudeInDegrees());
$viewOptions = array(
'startkey' => $startkey,
'endkey' => $endkey
);
$results = CBDatabase::$master->searchByView("dev_posts", "by_location", $viewOptions);
The build key function just follows the same rules as above in my view. and the GeoLocation class is taken from here: https://github.com/anthonymartin/GeoLocation.php
Request
I hope this really helps someone. It does work for me. If there is a much better way of doing this I'd love to hear constructive feedback.
I would now like to include a sort by date created within the range of keys. Order by Descending in the options won't work as this will order by the actual location key. The only way I can think of doing this is to include the date.created into the beginning of the key. But I'm not sure if this is the right way to go about this. I am very new to couchbase so guidance would be massively appreciated.
I try to compare two swim times in php. They are like HH:MM:SS.XX (XX are hundreths). I get them as string and i want to find out which swimmer is faster. I tryed to convert them using strtotime(). It works with hours, minutes and seconds but it ignores hundreths. Here is my code for better explanation:
$novy = strtotime($input1);
$stary = strtotime($input2);
if($novy < $stary){
//change old(stary) to new(novy)
}
If $input1 is 00:02:14.31 and $input2 is 00:02:14.32 both $novy and $stary are 1392850934.
I read some solution to similar problem in javascript but I can`t use it, this must be server-side.
Thank you for help.
If you use date_create_from_format you can specify the exact date format for php to convert the string representations to:
<?php
$input1 = '00:02:14.31';
$input2 = '00:02:14.32';
$novy = date_create_from_format('H:i:s.u', $input1);
$stary = date_create_from_format('H:i:s.u',$input2);
if ($novy < $stary) {
echo "1 shorter\n";
} else {
echo "2 longer\n";
}
Recommended reading: http://ie2.php.net/datetime.createfromformat
If the format is really HH:MM:SS.XX (ie: with leading 0's), you can just sort them alphabetically:
<?php
$input1 = '00:02:14.31';
$input2 = '00:02:14.32';
if ($input1 < $input2) {
echo "1 faster\n";
} else {
echo "2 faster\n";
}
It prints 1 faster
You could write some conditional logic to test if HH::MM::SS are identical, then simply compare XX, else use the strtotime() function that you are already using
You are working with durations, not dates. PHP's date and time functions aren't really of any help here. You should parse the string yourself to get a fully numeric duration:
$time = '00:02:14.31';
sscanf($time, '%d:%d:%d.%d', $hours, $minutes, $seconds, $centiseconds);
$total = $centiseconds
+ $seconds * 100
+ $minutes * 60 * 100
+ $hours * 60 * 60 * 100;
var_dump($total);
The total is in centiseconds (100th of a second, the scale of your original input). Multiply/divide by other factors to get in others scales, as needed.
There's a lot of examples on how to convert LDAP->Unix, but I can't for the love of god convert it back as in Unix->LDAP..
Here's what i've got for LDAP->Unix:
How to convert LDAP timestamp to Unix timestamp
http://www.morecavalier.com/index.php?whom=Apps%2FLDAP+timestamp+converter
function LDAPtoUnix($t) {
$secsAfterADepoch = $t / (100000000);
$AD2Unix = ( (1970-1601) * 365 -3 + round((1970-1601)/4) ) * 86400;
return intval($secsAfterADepoch-$AD2Unix);
}
Which i think should be accurate.
But I'm twisting my puny little brain to reverse the mathematics and i can't figure out it..
My head is boiling to just calculate the difference in seconds between the different epochs and simple adding/subtracting them to given time parameter?
Can someone shed some light on how to reverse the timestamp?
Reference: http://msdn.microsoft.com/en-us/library/windows/desktop/ms679430(v=vs.85).aspx
Also the main reason for why I'm asking besides my brain not wanting to compute that mathematics is that the floating point mechanism of PHP appears to be not so specific as it needs to be?
If i calculate Unix->LDAP timestamp, i'll end up with 1.3009518089E+17 and i'm not sure that Active Directory likes this particular notaion, so i would need to store it in a string but i can't figure out how to calculate these large numbers and not end up with a double.
At the moment i've got:
printf("%.0f", UnixToLDAP(time()));
which gives me the correct length but it's not really specific.
What i need
Short and simple,
Unix->LDAP timestamp that fits in pwdLastSet in Active Directory.
Also, it must be as perfect as possible, my attempts doesn't end well when checking: http://www.morecavalier.com/index.php?whom=Apps%2FLDAP+timestamp+converter
Google: Automatic solution (Windows 2012 Active Directory)
You can simply state -1 in pwdLastSet and Windows will automaticly set the time to the last login after the password was changed. It's not perfect but it works for anyone googling and ending up here.
You can make your function a bit more readable by naming the input parameter properly and assigning an intermediate variable before returning. Since $AD2Unix is actually a fixed term, it could probably be a constant; for now, I've just moved it to the top of the function:
function LDAPtoUnix($ldap_ts) {
$AD2Unix = ( (1970-1601) * 365 -3 + round((1970-1601)/4) ) * 86400;
$secsAfterADepoch = $ldap_ts / 100000000;
$unix_ts = intval( $secsAfterADepoch - $AD2Unix );
return $unix_ts;
}
We now have 2 lines to invert; they clearly need to happen in reverse order, and then using simple algebra to rearrange the terms in each (/ becomes *, - becomes +) we get this:
function UnixtoLDAP($unix_ts) {
$AD2Unix = ( (1970-1601) * 365 -3 + round((1970-1601)/4) ) * 86400;
$secsAfterADepoch = intval( $AD2Unix + $unix_ts );
$ldap_ts = $secsAfterADepoch * 100000000;
return $ldap_ts;
}
A couple of tests suggest this inverts the original nicely.
Don't use intval(), it will overflow on 32-bit PHP, just let PHP use float.
function toLdap($unixTimeStamp) {
return ($unixTimeStamp + 11644473600) * 10000000;
}
function toUnix($ldapTimeStamp) {
return (int) (($ldapTimeStamp / 10000000) - 11644473600);
}
And you can calculate the diff of Unix and LDAP time using DateTime (more readable)
$diff = (new DateTime('1970-01-01'))
->diff(new DateTime('1601-01-01'))
->days * (24 * 60 * 60);
echo $diff; // 11644473600
Something like this?
function UnixToLDAP($t) {
$AD2Unix = ( (1970-1601) * 365 -3 + round((1970-1601)/4) ) * 86400;
return intval($t+$AD2Unix) * 100000000;
}
I have 2 coordinates. Coordinate 1 is a 'person'. Coordinate 2 is a destination.
How do I move coordinate 1 100 meters closer to coordinate 2?
This would be used in a cron job, so only php and mysql included.
For example:
Person is at: 51.26667, 3.45417
Destination is: 51.575001, 4.83889
How would i calculate the new coordinates for Person to be 100 meters closer?
Use Haversine to calculate the difference between the two points in metres; then adjust the value of the person coordinates proportionally.
$radius = 6378100; // radius of earth in meters
$latDist = $lat - $lat2;
$lngDist = $lng - $lng2;
$latDistRad = deg2rad($latDist);
$lngDistRad = deg2rad($lngDist);
$sinLatD = sin($latDistRad);
$sinLngD = sin($lngDistRad);
$cosLat1 = cos(deg2rad($lat));
$cosLat2 = cos(deg2rad($lat2));
$a = ($sinLatD/2)*($sinLatD/2) + $cosLat1*$cosLat2*($sinLngD/2)*($sinLngD/2);
if($a<0) $a = -1*$a;
$c = 2*atan2(sqrt($a), sqrt(1-$a));
$distance = $radius*$c;
Feeding your values of:
$lat = 51.26667; // Just South of Aardenburg in Belgium
$lng = 3.45417;
$lat2 = 51.575001; // To the East of Breda in Holland
$lng2 = 4.83889;
gives a result of 102059.82251083 metres, 102.06 kilometers
The ratio to adjust by is 100 / 102059.82251083 = 0.0009798174985988102859004569070625
$newLat = $lat + (($lat2 - $lat) * $ratio);
$newLng = $lng + (($lng2 - $lng) * $ratio);
Gives a new latitude of 51.266972108109 and longitude of 3.4555267728867
Find the angle theta between the x-axis and the vector from person to destination.
theta = Atan2(dest.y-person.y, dest.x-person.x).
Now use theta and the amount you want to advance the point to calculate the new point.
newPoint.x = advanceDistance * cos(theta) + person.x
newPoint.y = advanceDistance * sin(theta) + person.y
If you understand JavaScript, you may want to check out the moveTowards() method in the following Stack Overflow post:
How to add markers on Google Maps polylines based on distance along the line?
This method returns the destination point when given a start point, an end point, and the distance to travel along that line. You can use point 1 as the starting point, point 2 as the end point, and a distance of 100 meters. It's written in JavaScript, but I'm sure it can be easily ported to PHP or MySQL.
You may also want to check out this other Stack Overflow post which implements a part of the above JavaScript implementation, as a user defined function for SQL Server 2008, called func_MoveTowardsPoint:
Moving a Point along a Path in SQL Server 2008
The above uses SQL Server 2008's in-built geography data type. However you can easily use two decimal data types for latitude and longitude in place of the single geography data type.
Both the SQL Server and the JavaScript examples were based on implementations from Chris Veness's article Calculate distance, bearing and more between Latitude/Longitude points.
Take a look at the map coordinates on this page. This is linked in from Wikipedia and the coordinates are passed the query string. I'm not sure of the actual terms for this but How do I convert the coordinates? They look like this:
37° 14′ 6″ N, 115° 48′ 40″ W
I would like them to look like this:
37.235, -115.811111
, which is a format readable by Google maps, as seen in this example.
How do I do this in PHP, and what are the two different types of coordinates called?
The original format is in hours, minutes, seconds format. To convert to decimal, do:
D = H + M/60 + s/3600
So in your example, 37,14,6 becomes
37 + 14/60 + 6/3600 = 37.235, as stated.
If the latitude is N the result is positive, if S, negative.
If the longitude is E the result is positive. If west the result is negative.
Here is the Javascript function that does what you are talking about.
This utility permits the user to convert latitude and longitude
between decimal degrees and degrees, minutes, and seconds. For
convenience, a link is included to the National Geodetic Survey's
NADCON program, which allows conversions between the NAD83 / WGS84
coordinate system and the older NAD27 coordinate system. NAD27
coordinates are presently used for broadcast authorizations and
applications.
This utility requires that Javascript be enabled to perform the
calculations.
My PHP Code from this Post, Thank You!
function GetLatLon_FromExif($GPS){
$Lat_Ref = $GPS['GPSLatitudeRef'];
if($Lat_Ref == "S") { $Lat_Neg = "-"; }
$Lat_H = explode("/" ,$GPS['GPSLatitude']['0'])[0];
$Lat_M = explode("/" ,$GPS['GPSLatitude']['1'])[0];
$Lat_S = explode("/" ,$GPS['GPSLatitude']['2'])[0];
$Lat_S2 = explode("/" ,$GPS['GPSLatitude']['2'])[1];
$Lon_Ref = $GPS['GPSLongitudeRef'];
if($Lon_Ref == "W") { $Lon_Neg = "-"; }
$Lon_H = explode("/" ,$GPS['GPSLongitude']['0'])[0];
$Lon_M = explode("/" ,$GPS['GPSLongitude']['1'])[0];
$Lon_S = explode("/" ,$GPS['GPSLongitude']['2'])[0];
$Lon_S2 = explode("/" ,$GPS['GPSLongitude']['2'])[1];
$Lat = $Lat_H+($Lat_M/60)+(($Lat_S/$Lat_S2)/3600);
$Lon = $Lon_H+($Lon_M/60)+(($Lon_S/$Lon_S2)/3600);
return $Lat_Neg.$Lat.",".$Lon_Neg.$Lon;
}
JSON (from IMAGE EXIF):
"GPS": {
"GPSLatitudeRef": "N",
"GPSLatitude": [
"22/1",
"15/1",
"1708/100"
],
"GPSLongitudeRef": "W",
"GPSLongitude": [
"97/1",
"52/1",
"1882/100"
],
"GPSAltitudeRef": "\u0000",
"GPSAltitude": "9364/369",
"GPSSpeedRef": "K",
"GPSSpeed": "2203/12629",
"GPSImgDirectionRef": "T",
"GPSImgDirection": "52751/159",
"GPSDestBearingRef": "T",
"GPSDestBearing": "52751/159",
"UndefinedTag:0x001F": "6625/828"
}
Umm the page you mention already gives you the coordinates in WGS84 and in Latitude Longitude format