Where should the code added on feature/#578_UTM_zone_tools live?
Created by: das-g
Do not merge
For now, for discussing what to do with this. (Might be turned into a merge request later, depending on result.)
This new code for #578 (closed) is fairly independent of the rest of the project. Should it live within the osmaxx
repo or in a separate (and POPyO) PyPI package?
Some of the tests would have to remain in osmaxx
as they depend on PostGIS, lest we want the PyPI package to have that as a test dependency, too.
Do not merge
Merge request reports
Activity
Created by: hixi
IMHO think this is a good candidate for an external package.
I'd make an PyPI-Package (using twine to push to PyPI).
I see two options (and a combination thereof):
-
making it an independent package, no connection to django/django-rest-frameworkwe're depending on GeoDjango. - making it a django-rest-framework-package
doing both, and using the first as a dependency on the second
I would favor option 2Option 2 is the only Option: Having it ready as a REST-Service, this can easily be included in a Dockerimage for simple deployment and be used for the OSMaxx project right away, but doesn't limit usage in any other way(one should easily be able to use the package without installing rest-framework, if the viewsets aren't being used).Doing the review I discovered my assumption that this can be independent of Django doesn't hold. Hence the crossed out lines above.
Since we rely on GeoDjango, we should make that a dependency and the tests should cover that (relying on postgis!), escpecially since that isn't that hard to do with automatic testing even on travis
-
1 1 def pytest_addoption(parser): 2 2 parser.addoption("--runslow", action="store_true", 3 3 help="run slow tests") 4 parser.addoption("--all-utm-zones", action="store_true", Created by: hixi
instead of running the test with really all utm zones, I wonder if it would make sense to test the edge cases, boundary cases and happy path cases only. This would require more thought on what those cases actually are, but just because we
can
test all cases, doesn't mean we necessarilyneed
to (they seem to me quite similar in what they test).
43 ) 44 if xmin <= xmax: 45 domain = Polygon.from_bbox((xmin, ymin, xmax, ymax)) 46 domain.srid = WGS_84 47 return domain 48 else: 49 # cut at idealized international date line 50 return MultiPolygon( 51 Polygon.from_bbox((xmin, ymin, MAX_LONGITUDE_DEGREES, ymax)), 52 Polygon.from_bbox((MIN_LONGITUDE_DEGREES, ymin, xmax, ymax)), 53 srid=WGS_84, 54 ) 55 56 @property 57 def srid(self): 58 return self.HEMISPHERE_PREFIXES[self.hemisphere] * 100 + self.utm_zone_number 47 return domain 48 else: 49 # cut at idealized international date line 50 return MultiPolygon( 51 Polygon.from_bbox((xmin, ymin, MAX_LONGITUDE_DEGREES, ymax)), 52 Polygon.from_bbox((MIN_LONGITUDE_DEGREES, ymin, xmax, ymax)), 53 srid=WGS_84, 54 ) 55 56 @property 57 def srid(self): 58 return self.HEMISPHERE_PREFIXES[self.hemisphere] * 100 + self.utm_zone_number 59 60 @property 61 def central_meridian_longitude_degrees(self): 62 return MIN_LONGITUDE_DEGREES + (self.utm_zone_number - 0.5) * self.ZONE_WIDTH_DEGREES 49 # cut at idealized international date line 50 return MultiPolygon( 51 Polygon.from_bbox((xmin, ymin, MAX_LONGITUDE_DEGREES, ymax)), 52 Polygon.from_bbox((MIN_LONGITUDE_DEGREES, ymin, xmax, ymax)), 53 srid=WGS_84, 54 ) 55 56 @property 57 def srid(self): 58 return self.HEMISPHERE_PREFIXES[self.hemisphere] * 100 + self.utm_zone_number 59 60 @property 61 def central_meridian_longitude_degrees(self): 62 return MIN_LONGITUDE_DEGREES + (self.utm_zone_number - 0.5) * self.ZONE_WIDTH_DEGREES 63 64 def __eq__(self, other): 61 def central_meridian_longitude_degrees(self): 62 return MIN_LONGITUDE_DEGREES + (self.utm_zone_number - 0.5) * self.ZONE_WIDTH_DEGREES 63 64 def __eq__(self, other): 65 return self.hemisphere, self.utm_zone_number == other.hemisphere, other.utm_zone_number 66 67 def __hash__(self): 68 return hash((self.hemisphere, self.utm_zone_number)) 69 70 def __str__(self): 71 return "UTM Zone {zone_number}, {hemisphere}ern hemisphere".format( 72 zone_number=self.utm_zone_number, 73 hemisphere=self.hemisphere, 74 ) 75 76 def __repr__(self): Created by: hixi
From the
__repr__
documentation: I think this should returnUniversalTransverseMercatorZone({hemisphere}, {zone_number})
.I know, there is a shorthand below, but this class shouldn't know about that.
Alternatively, rename this class.
80 ) 81 82 UTMZone = UniversalTransverseMercatorZone 83 UTM_ZONE_NUMBERS = UTMZone.VALID_ZONE_NUMBERS 84 ALL_UTM_ZONES = frozenset(UTMZone(hs, nr) for hs in UTMZone.HEMISPHERE_PREFIXES for nr in UTM_ZONE_NUMBERS) 85 86 87 def utm_zones_for_representing(geom): 88 return frozenset(zone for zone in ALL_UTM_ZONES if zone.can_represent(geom)) 89 90 91 def wrap_longitude_degrees(longitude_degrees): 92 return confine(longitude_degrees, MIN_LONGITUDE_DEGREES, MAX_LONGITUDE_DEGREES) 93 94 95 def confine(value, lower_bound, upper_bound): 43 ) 44 if xmin <= xmax: 45 domain = Polygon.from_bbox((xmin, ymin, xmax, ymax)) 46 domain.srid = WGS_84 47 return domain 48 else: 49 # cut at idealized international date line 50 return MultiPolygon( 51 Polygon.from_bbox((xmin, ymin, MAX_LONGITUDE_DEGREES, ymax)), 52 Polygon.from_bbox((MIN_LONGITUDE_DEGREES, ymin, xmax, ymax)), 53 srid=WGS_84, 54 ) 55 56 @property 57 def srid(self): 58 return self.HEMISPHERE_PREFIXES[self.hemisphere] * 100 + self.utm_zone_number 43 ) 44 if xmin <= xmax: 45 domain = Polygon.from_bbox((xmin, ymin, xmax, ymax)) 46 domain.srid = WGS_84 47 return domain 48 else: 49 # cut at idealized international date line 50 return MultiPolygon( 51 Polygon.from_bbox((xmin, ymin, MAX_LONGITUDE_DEGREES, ymax)), 52 Polygon.from_bbox((MIN_LONGITUDE_DEGREES, ymin, xmax, ymax)), 53 srid=WGS_84, 54 ) 55 56 @property 57 def srid(self): 58 return self.HEMISPHERE_PREFIXES[self.hemisphere] * 100 + self.utm_zone_number 47 return domain 48 else: 49 # cut at idealized international date line 50 return MultiPolygon( 51 Polygon.from_bbox((xmin, ymin, MAX_LONGITUDE_DEGREES, ymax)), 52 Polygon.from_bbox((MIN_LONGITUDE_DEGREES, ymin, xmax, ymax)), 53 srid=WGS_84, 54 ) 55 56 @property 57 def srid(self): 58 return self.HEMISPHERE_PREFIXES[self.hemisphere] * 100 + self.utm_zone_number 59 60 @property 61 def central_meridian_longitude_degrees(self): 62 return MIN_LONGITUDE_DEGREES + (self.utm_zone_number - 0.5) * self.ZONE_WIDTH_DEGREES Created by: das-g
UTM Zone 1 goes from longitude (-180°) to longitude (-180° + zone width). Its central meridian is centered between these boundaries.
- 180° + (1 - 0) × zone width gives us the east edge of zone 1.
- 180° + (1 - ½) × zone width gives us the central meridian of zone 1.
- 180° + (1 - 1) × zone width gives us the west edge of zone 1.
where the first
1
is the zone number.Not all other zones have the same width (at least not everywhere), but their "central meridians" are defined consistent with that formula (and thus not always actually central).
61 def central_meridian_longitude_degrees(self): 62 return MIN_LONGITUDE_DEGREES + (self.utm_zone_number - 0.5) * self.ZONE_WIDTH_DEGREES 63 64 def __eq__(self, other): 65 return self.hemisphere, self.utm_zone_number == other.hemisphere, other.utm_zone_number 66 67 def __hash__(self): 68 return hash((self.hemisphere, self.utm_zone_number)) 69 70 def __str__(self): 71 return "UTM Zone {zone_number}, {hemisphere}ern hemisphere".format( 72 zone_number=self.utm_zone_number, 73 hemisphere=self.hemisphere, 74 ) 75 76 def __repr__(self): Created by: das-g
How does using the alias class name violate
[T]his should look like a valid Python expression that could be used to recreate an object with the same value (given an appropriate environment).
in any way? Or is it in opposition to something else in the documentation?
47 return domain 48 else: 49 # cut at idealized international date line 50 return MultiPolygon( 51 Polygon.from_bbox((xmin, ymin, MAX_LONGITUDE_DEGREES, ymax)), 52 Polygon.from_bbox((MIN_LONGITUDE_DEGREES, ymin, xmax, ymax)), 53 srid=WGS_84, 54 ) 55 56 @property 57 def srid(self): 58 return self.HEMISPHERE_PREFIXES[self.hemisphere] * 100 + self.utm_zone_number 59 60 @property 61 def central_meridian_longitude_degrees(self): 62 return MIN_LONGITUDE_DEGREES + (self.utm_zone_number - 0.5) * self.ZONE_WIDTH_DEGREES 80 ) 81 82 UTMZone = UniversalTransverseMercatorZone 83 UTM_ZONE_NUMBERS = UTMZone.VALID_ZONE_NUMBERS 84 ALL_UTM_ZONES = frozenset(UTMZone(hs, nr) for hs in UTMZone.HEMISPHERE_PREFIXES for nr in UTM_ZONE_NUMBERS) 85 86 87 def utm_zones_for_representing(geom): 88 return frozenset(zone for zone in ALL_UTM_ZONES if zone.can_represent(geom)) 89 90 91 def wrap_longitude_degrees(longitude_degrees): 92 return confine(longitude_degrees, MIN_LONGITUDE_DEGREES, MAX_LONGITUDE_DEGREES) 93 94 95 def confine(value, lower_bound, upper_bound): Created by: das-g
Kinda unspecific for what it does, IMHO.
Let's see if http://math.stackexchange.com/questions/1851903/does-this-modulo-based-function-have-a-name brings up a better one.
61 def central_meridian_longitude_degrees(self): 62 return MIN_LONGITUDE_DEGREES + (self.utm_zone_number - 0.5) * self.ZONE_WIDTH_DEGREES 63 64 def __eq__(self, other): 65 return self.hemisphere, self.utm_zone_number == other.hemisphere, other.utm_zone_number 66 67 def __hash__(self): 68 return hash((self.hemisphere, self.utm_zone_number)) 69 70 def __str__(self): 71 return "UTM Zone {zone_number}, {hemisphere}ern hemisphere".format( 72 zone_number=self.utm_zone_number, 73 hemisphere=self.hemisphere, 74 ) 75 76 def __repr__(self): 47 return domain 48 else: 49 # cut at idealized international date line 50 return MultiPolygon( 51 Polygon.from_bbox((xmin, ymin, MAX_LONGITUDE_DEGREES, ymax)), 52 Polygon.from_bbox((MIN_LONGITUDE_DEGREES, ymin, xmax, ymax)), 53 srid=WGS_84, 54 ) 55 56 @property 57 def srid(self): 58 return self.HEMISPHERE_PREFIXES[self.hemisphere] * 100 + self.utm_zone_number 59 60 @property 61 def central_meridian_longitude_degrees(self): 62 return MIN_LONGITUDE_DEGREES + (self.utm_zone_number - 0.5) * self.ZONE_WIDTH_DEGREES 47 return domain 48 else: 49 # cut at idealized international date line 50 return MultiPolygon( 51 Polygon.from_bbox((xmin, ymin, MAX_LONGITUDE_DEGREES, ymax)), 52 Polygon.from_bbox((MIN_LONGITUDE_DEGREES, ymin, xmax, ymax)), 53 srid=WGS_84, 54 ) 55 56 @property 57 def srid(self): 58 return self.HEMISPHERE_PREFIXES[self.hemisphere] * 100 + self.utm_zone_number 59 60 @property 61 def central_meridian_longitude_degrees(self): 62 return MIN_LONGITUDE_DEGREES + (self.utm_zone_number - 0.5) * self.ZONE_WIDTH_DEGREES Created by: hixi
or a docstring:
""" Some short, meaningful description. UTM Zone 1 goes from longitude (-180°) to longitude (-180° + zone width). Its central meridian is centered between these boundaries. 180° + (1 - 0) × zone width gives us the east edge of zone 1. 180° + (1 - ½) × zone width gives us the central meridian of zone 1. 180° + (1 - 1) × zone width gives us the west edge of zone 1. where the first 1 is the zone number. returns `Bob Lawbla` """
1 from django.contrib.gis.geos.collections import MultiPolygon 2 from django.contrib.gis.geos.polygon import Polygon 3 4 from osmaxx.conversion_api.coordinate_reference_systems import WGS_84 5 6 MIN_LONGITUDE_DEGREES = -180 7 MAX_LONGITUDE_DEGREES = +180 8 9 10 class UTMZone: 11 """ 12 Universal Transverse Mercator (UTM) zone Created by: das-g
DRF package it is: https://github.com/geometalab/drf-utm-zone-info