forked from pegler/pytzwhere
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtzwhere.py
executable file
·159 lines (134 loc) · 7.33 KB
/
tzwhere.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#!/usr/bin/python
import argparse
import simplejson as json
import datetime
import math
import pickle
parser = argparse.ArgumentParser(description='''
Convert lat/lng to timezones. Specify --read_pickle to initialize from a pickle file instead of the json file.
''')
parser.add_argument('--json_file', default='tz_world_compact.json',
help='path to the json input file')
parser.add_argument('--pickle_file', default='tz_world.pickle',
help='path to the json input file')
parser.add_argument('--read_pickle', action='store_true',
help='read pickle data instead of json')
parser.add_argument('--write_pickle', action='store_true',
help='whether to output a pickle file')
args = parser.parse_args()
class tzwhere(object):
SHORTCUT_DEGREES_LATITUDE = 1
SHORTCUT_DEGREES_LONGITUDE = 1
def __init__(self):
if args.read_pickle:
filename = args.pickle_file
else:
filename = args.json_file
input_file = open(filename, 'r')
if args.read_pickle:
print 'Reading pickle input file: %s' % filename
featureCollection = pickle.load(input_file)
else:
print 'Reading json input file: %s' % filename
featureCollection = json.load(input_file)
input_file.close()
if args.write_pickle:
print 'Writing pickle output file: %s' % PICKLE_FILENAME
f = open(PICKLE_FILENAME, 'w')
pickle.dump(featureCollection, f, pickle.HIGHEST_PROTOCOL)
f.close()
self.timezoneNamesToPolygons = {}
for feature in featureCollection['features']:
tzname = feature['properties']['TZID']
region = tzname.split('/')[0]
if feature['geometry']['type'] == 'Polygon':
polys = feature['geometry']['coordinates']
if polys and not (tzname in self.timezoneNamesToPolygons):
self.timezoneNamesToPolygons[tzname] = []
for raw_poly in polys:
#WPS84 coordinates are [long, lat], while many conventions are [lat, long]
#Our data is in WPS84. Convert to an explicit format which geolib likes.
assert len(raw_poly)%2 == 0
poly = []
while raw_poly:
lat = raw_poly.pop()
lng = raw_poly.pop()
poly.append({'lat': lat, 'lng': lng})
self.timezoneNamesToPolygons[tzname].append(tuple(poly))
self.timezoneLongitudeShortcuts = {};
self.timezoneLatitudeShortcuts = {};
for tzname in self.timezoneNamesToPolygons:
for polyIndex, poly in enumerate(self.timezoneNamesToPolygons[tzname]):
lats = [x['lat'] for x in poly]
lngs = [x['lng'] for x in poly]
minLng = math.floor(min(lngs) / self.SHORTCUT_DEGREES_LONGITUDE) * self.SHORTCUT_DEGREES_LONGITUDE;
maxLng = math.floor(max(lngs) / self.SHORTCUT_DEGREES_LONGITUDE) * self.SHORTCUT_DEGREES_LONGITUDE;
minLat = math.floor(min(lats) / self.SHORTCUT_DEGREES_LATITUDE) * self.SHORTCUT_DEGREES_LATITUDE;
maxLat = math.floor(max(lats) / self.SHORTCUT_DEGREES_LATITUDE) * self.SHORTCUT_DEGREES_LATITUDE;
degree = minLng
while degree <= maxLng:
if degree not in self.timezoneLongitudeShortcuts:
self.timezoneLongitudeShortcuts[degree] = {}
if tzname not in self.timezoneLongitudeShortcuts[degree]:
self.timezoneLongitudeShortcuts[degree][tzname] = []
self.timezoneLongitudeShortcuts[degree][tzname].append(polyIndex)
degree = degree + self.SHORTCUT_DEGREES_LONGITUDE
degree = minLat
while degree <= maxLat:
if degree not in self.timezoneLatitudeShortcuts:
self.timezoneLatitudeShortcuts[degree] = {}
if tzname not in self.timezoneLatitudeShortcuts[degree]:
self.timezoneLatitudeShortcuts[degree][tzname] = []
self.timezoneLatitudeShortcuts[degree][tzname].append(polyIndex)
degree = degree + self.SHORTCUT_DEGREES_LATITUDE
#convert things to tuples to save memory
for tzname in self.timezoneNamesToPolygons.keys():
self.timezoneNamesToPolygons[tzname] = tuple(self.timezoneNamesToPolygons[tzname])
for degree in self.timezoneLatitudeShortcuts:
for tzname in self.timezoneLatitudeShortcuts[degree].keys():
self.timezoneLatitudeShortcuts[degree][tzname] = tuple(self.timezoneLatitudeShortcuts[degree][tzname])
for degree in self.timezoneLongitudeShortcuts.keys():
for tzname in self.timezoneLongitudeShortcuts[degree].keys():
self.timezoneLongitudeShortcuts[degree][tzname] = tuple(self.timezoneLongitudeShortcuts[degree][tzname])
def _point_inside_polygon(self, x, y, poly):
n = len(poly)
inside =False
p1x, p1y = poly[0]['lng'], poly[0]['lat']
for i in range(n+1):
p2x,p2y = poly[i % n]['lng'], poly[i % n]['lat']
if y > min(p1y,p2y):
if y <= max(p1y,p2y):
if x <= max(p1x,p2x):
if p1y != p2y:
xinters = (y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
if p1x == p2x or x <= xinters:
inside = not inside
p1x,p1y = p2x,p2y
return inside
def tzNameAt(self, latitude, longitude):
latTzOptions = self.timezoneLatitudeShortcuts[math.floor(latitude / self.SHORTCUT_DEGREES_LATITUDE) * self.SHORTCUT_DEGREES_LATITUDE]
latSet = set(latTzOptions.keys());
lngTzOptions = self.timezoneLongitudeShortcuts[math.floor(longitude / self.SHORTCUT_DEGREES_LONGITUDE) * self.SHORTCUT_DEGREES_LONGITUDE]
lngSet = set(lngTzOptions.keys())
possibleTimezones = lngSet.intersection(latSet);
if possibleTimezones:
if False and len(possibleTimezones) == 1:
return possibleTimezones.pop()
else:
for tzname in possibleTimezones:
polyIndices = set(latTzOptions[tzname]).intersection(set(lngTzOptions[tzname]));
for polyIndex in polyIndices:
poly = self.timezoneNamesToPolygons[tzname][polyIndex];
if self._point_inside_polygon(longitude, latitude, poly):
return tzname
if __name__ == "__main__":
start = datetime.datetime.now()
w=tzwhere()
end = datetime.datetime.now()
print 'Initialized in: ',
print end-start
print w.tzNameAt(float(35.295953), float(-89.662186)) #Arlington, TN
print w.tzNameAt(float(33.58), float(-85.85)) #Memphis, TN
print w.tzNameAt(float(61.17), float(-150.02)) #Anchorage, AK
print w.tzNameAt(float(44.12), float(-123.22)) #Eugene, OR
print w.tzNameAt(float(42.652647), float(-73.756371)) #Albany, NY