Created
May 28, 2016 18:58
-
-
Save Xowap/69edfaeec148b7ba54744c2680c863f8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from datetime import tzinfo, timedelta | |
| from math import floor | |
| from django.contrib.gis.db.models.fields import PolygonField | |
| from django.contrib.gis.db.models.manager import GeoManager | |
| from django.contrib.gis.geos.geometry import GEOSGeometry | |
| from django.db import models | |
| from osgeo import ogr | |
| from pytz import timezone | |
| from pytz.exceptions import UnknownTimeZoneError | |
| class TimeZoneManager(GeoManager): | |
| def import_shapefile(self, path): | |
| """ | |
| Imports timezone shapes from the given path. | |
| The expected input file is Eric Muller's tz_world | |
| http://efele.net/maps/tz/world/ | |
| :param path: | |
| :return: | |
| """ | |
| source = ogr.Open(path) | |
| if source is None: | |
| raise IOError('The source file {} is invalid or inexistant'.format(path)) | |
| layer = source.GetLayer(0) | |
| time_zones = [] | |
| for feature in layer: | |
| geometry = GEOSGeometry(str(feature.GetGeometryRef())) | |
| polygons = [] | |
| if geometry.geom_type == 'Polygon': | |
| polygons = [geometry] | |
| elif geometry.geom_type == 'MultiPolygon': | |
| polygons = geometry | |
| for polygon in polygons: | |
| time_zones.append(TimeZone( | |
| zone=polygon, | |
| tzid=feature.GetFieldAsString('TZID'), | |
| )) | |
| self.all().delete() | |
| self.bulk_create(time_zones) | |
| def tzguess_at(self, geometry): | |
| """ | |
| Returns the time zone name at the given geometry, if known. | |
| :param geometry: | |
| :return: | |
| """ | |
| point = geometry.centroid | |
| if point.srid is not None: | |
| point.transform(4326) | |
| try: | |
| tzid = self.filter(zone__contains=point)[0].tzid | |
| return tzid, point | |
| except IndexError: | |
| return None, point | |
| def tzinfo_name_at(self, geometry): | |
| """ | |
| Calculates the time zone that applies at the given geometry's centroid. | |
| Basically, you sould always pass a point as geometry. | |
| :param geometry: | |
| :return: | |
| """ | |
| tzname, point = self.tzguess_at(geometry) | |
| if tzname is not None: | |
| try: | |
| return timezone(tzname), tzname | |
| except UnknownTimeZoneError: | |
| pass | |
| tz = InternationalWaterTZ(point.x) | |
| return InternationalWaterTZ(point.x), tz.tzname(None) | |
| def tzinfo_at(self, geometry): | |
| return self.tzinfo_name_at(geometry)[0] | |
| class TimeZone(models.Model): | |
| objects = TimeZoneManager() | |
| zone = PolygonField(spatial_index=True) | |
| tzid = models.CharField(max_length=100) | |
| class InternationalWaterTZ(tzinfo): | |
| """ | |
| Generates a tzinfo for a given point in the international waters | |
| """ | |
| def __init__(self, longitude): | |
| super(InternationalWaterTZ, self).__init__() | |
| self.hours_shift = self.calculate_hour_shift(longitude) | |
| self.time_shift = timedelta( | |
| hours=self.hours_shift | |
| ) | |
| def __eq__(self, other): | |
| if isinstance(other, InternationalWaterTZ): | |
| return self.hours_shift == other.hours_shift | |
| else: | |
| return False | |
| def __repr__(self): | |
| return "<InternationalWaterTz {}>".format(self.tzname(None)) | |
| @staticmethod | |
| def calculate_hour_shift(longitude): | |
| return int(floor(((longitude + 180) % 360 - 180 + 7.5) / 15)) | |
| def tzname(self, date_time): | |
| return "GMT{:+d}".format(self.hours_shift) | |
| def utcoffset(self, date_time): | |
| return self.time_shift | |
| def dst(self, date_time): | |
| return self.time_shift | |
| def localize(self, dt, is_dst=False): | |
| """ | |
| Convert naive time to local time | |
| """ | |
| if dt.tzinfo is not None: | |
| raise ValueError('Not naive datetime (tzinfo is already set)') | |
| return dt.replace(tzinfo=self) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment