不使用类似Geonames.org的网络服务,如何通过经纬度确定时区?

53

有没有可能在不使用网络服务的情况下确定点(lat / lon)的时区? Geonames.org对我来说不够稳定 :( 我需要这个在PHP中工作。

谢谢


Geonames提供商业/付费服务以及免费服务。你尝试过那些付费服务吗? - winwaed
7
你可以下载geonames数据库,自己进行托管,并将纬度/经度与数据库中最近的地点(带有时区)进行比较吗? - Adam Hopkinson
参见:如何从位置获取时区? - Matt Johnson-Pint
这里有一个相关的问题,其中给出了详细的可能性列表:https://dev59.com/UGQo5IYBdhLWcg3wZepg#16086964 - jannikmi
18个回答

57

我之前也遇到过这个问题,就像adam建议的那样:

  • 从geonames.org下载城市数据库
  • 将其转换为紧凑的纬度/经度→时区列表
  • 使用R-Tree实现来高效地查找给定坐标的最近城市(或者说它的时区)

如果我没记错的话,它只需要不到1秒钟来填充R-Tree,然后可以每秒执行数千次查找(在一台5年前的PC上都能跑得很好)。


1
抱歉,如果这不是有效的,但是R-Tree不是用于矩形吗?我认为最近邻算法,例如使用kd树的算法是用于点的。 - Markos Fragkakis
2
@Markos Fragkakis:刚看到你的评论。我链接的R-Tree库有最近邻搜索功能。不确定它是如何实现的,但基本思路应该适用于不同的数据结构。 - Michael Borgwardt
17
我曾经为“将其转换为紧凑的经纬度->时区列表”部分付出了很多痛苦,这里有代码:https://gist.github.com/1769458。 然后我使用了带有PostGIS的PostgreSQL,利用Geography类型的Gist索引,运行以下查询:select timezone_string where ST_DWithin(ST_GeographyFromText('SRID=4326;POINT(2.36 48.86)'), location, 100000, false) order by st_distance(ST_GeographyFromText('SRID=4326;POINT(2.36 48.86)'), location) limit 1。 我在所需点(2.36 48.86)周围100公里范围内进行搜索以获得良好的性能:30毫秒。如果失败了,我会扩大范围到6000公里:1秒 :) - Laurent Debricon
5
我创建了一个简单的Python脚本,用于将GeoNames.org数据(和其他数据)转换为各种格式,例如用于地理空间查询的MongoDB .json插入脚本。你可以在这里找到它:https://github.com/NuSkooler/GeoToTimeZone - NuSkooler
1
@hennings:我认为那样做行不通——最近的城市可能在树的完全不同的分支中。 - Michael Borgwardt
显示剩余5条评论

22

你需要多精确的结果?如果粗略估计足够了,就自己计算偏差:

offset = direction * longitude * 24 / 360

方向为1表示东,为-1表示西,经度在(-180, 180)之间。


非常感谢您的回答,如果准确性不是问题,那么会非常有帮助。我也在您的公式中四舍五入了小数。 - KarthikS
这只是一个非常粗略的估计,只有在你经常接受错误的时区(例如整个西班牙/法国与伦敦拥有相同的经度但有不同的时区)的情况下才能使用。(地图链接:https://i.imgur.com/5mcXA7n.jpg) - fregante
为什么必须乘以“direction”?对于经度=-90,它给出了-6小时,这很好,不需要乘以-1。 - Anton Bryzgalov
等效,但更简单:偏移=经度/15 - Greg Miller

10

我在处理另一个项目时遇到了这个问题,并深入研究了它。 我发现所有现有的解决方案在很大程度上都存在缺陷。

下载GeoNames数据并使用某些空间索引查找最近点肯定是一种选择,并且通常会产生正确的结果,但如果查询点与数据库中最近点之间跨越时区边界,它很容易失败。

更准确的方法是使用时区的数字地图,并编写代码以查找包含给定查询点的多边形。 幸运的是,在http://efele.net/maps/tz/world/(不再维护anymore)提供了世界时区的出色地图。 要编写高效的查询引擎,您需要:

  • 将ESRI shapefile格式解析成有用的内部表示形式。
  • 编写点在多边形内的代码,以测试给定查询点是否在给定多边形内。
  • 在多边形数据之上编写高效的空间索引,以便您不需要检查每个多边形来查找包含它的多边形。
  • 处理未包含在任何多边形中的查询(例如,在海洋中)。 在这种情况下,您应该“捕捉到”最近的多边形,直到一定距离,并在开放海洋中恢复“自然”时区(仅由经度确定的时区)。 为此,您将需要编写代码来计算查询点与多边形线段之间的距离(这是非平凡的,因为纬度和经度是非欧几里得坐标系),并且您的空间索引将需要能够返回附近的多边形,而不仅仅是可能包含的多边形。

每一个都值得有自己的Stack Overflow问题/答案页面。

在得出没有任何现有解决方案满足我的需求的结论后,我编写了自己的解决方案,并在这里提供了它:

http://askgeo.com

AskGeo使用数字地图和高度优化的空间索引,使得在单个线程上在我的计算机上运行超过10,000个查询每秒都是可能的。它是线程安全的,因此甚至可以实现更高的吞吐量。这是一段严肃的代码,并且我们花了很长时间来开发它,所以我们提供商业许可证。

它是用Java编写的,因此在PHP中使用它需要使用:

http://php-java-bridge.sourceforge.net/doc/how_it_works.php

我们也愿意为奖金移植它。有关定价和详细文档,请参见http://askgeo.com
我希望这对我正在处理的项目有所帮助。

8

你不需要一个Web服务来实现这个。 - themihai

5

1
好的。这不是非常计算密集吗? - Jacek Francuz
7
请注意,对于大多数情况,您实际上需要区分400多个行政时区,而不仅仅是基本偏移量,这是由于不同的夏令时切换和历史变化所致。 - Michael Borgwardt
这是很好的东西,但数据不应该是动态的而不是静态的吗?=) - Pacerier

5

对于陆地上的区域,已经制作了一些关于tz(Olson)数据库时区的shapefile地图。虽然它们的更新频率不如tz数据库本身那么快,但这是一个很好的起点,并且在大多数情况下似乎非常准确。


2
+1 Hi Tim,谢谢--我成功地在我的项目中使用了这些地图,利用GeoTools将经纬度转换为时区ID,然后使用Joda时间将瞬时时间加上时区ID转换为本地化历史时间。太棒了。 - user206428

4
这个怎么样?
// 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 'none?';
}
//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') ;

1
你能否添加一些注释来解释这个函数的工作原理? - codecowboy
1
@codecowboy 正在搜索整个时区列表,每个时区都指定了一个时区的质心 - 这并不可靠,但由于缺乏更好的方法,您可以考虑这是一种“最佳猜测”的实现。 - mindplay.dk

2
我已经编写了一个小的Java类来实现此功能。它可以很容易地转换为PHP。数据库嵌入在代码中。精度为22公里。
整个代码基本上就是这样的东西: https://sites.google.com/a/edval.biz/www/mapping-lat-lng-s-to-timezones
         if (lng < -139.5) {
          if (lat < 68.5) {
           if (lng < -140.5) {
            return 371;
           } else {
            return 325;
           }

因此,我认为将其翻译成PHP应该很容易。


感谢分享。这可以通过bash集成,但@flexjack明确表示他们需要在PHP中工作。 - Alastair
这可以翻译成几乎任何语言,但将数据放入代码中会导致更新不便,而且在30K+行的情况下性能可能很差。 如果您更新PHP答案,我有兴趣测试结果! :-) - Alastair

1

很遗憾,时区并不规则,因此有些简单的功能无法实现。请参考维基百科-时区中的地图。

然而,可以计算出一些非常粗略的近似值:1小时的差异对应15度经度(360/24)。


1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接