paul@0 | 1 | # -*- coding: iso-8859-1 -*- |
paul@0 | 2 | """ |
paul@0 | 3 | MoinMoin - LocationSupport library (derived from EventAggregatorSupport) |
paul@0 | 4 | |
paul@48 | 5 | @copyright: 2011, 2012, 2013 by Paul Boddie <paul@boddie.org.uk> |
paul@0 | 6 | @license: GNU GPL (v2 or later), see COPYING.txt for details. |
paul@0 | 7 | """ |
paul@0 | 8 | |
paul@0 | 9 | import operator |
paul@0 | 10 | import re |
paul@0 | 11 | |
paul@0 | 12 | location_normalised_regexp = re.compile( |
paul@0 | 13 | ur"(?:\d+\w*\s+)?" # preceding postcode (optional) |
paul@0 | 14 | ur"(?P<location>" # start of group of interest |
paul@0 | 15 | ur"\w[\w\s-]+?" # area or town |
paul@0 | 16 | ur"(?:,(?:\s*[\w-]+)+)?" # country (optional) |
paul@0 | 17 | ur")$", re.UNICODE) |
paul@0 | 18 | |
paul@0 | 19 | # Utility functions. |
paul@0 | 20 | |
paul@0 | 21 | def sign(x): |
paul@0 | 22 | if x < 0: |
paul@0 | 23 | return -1 |
paul@0 | 24 | else: |
paul@0 | 25 | return 1 |
paul@0 | 26 | |
paul@0 | 27 | # Location-related functions. |
paul@0 | 28 | |
paul@0 | 29 | class Reference: |
paul@0 | 30 | |
paul@0 | 31 | "A map reference." |
paul@0 | 32 | |
paul@0 | 33 | def __init__(self, degrees, minutes=0, seconds=0): |
paul@0 | 34 | self.degrees = degrees |
paul@0 | 35 | self.minutes = minutes |
paul@0 | 36 | self.seconds = seconds |
paul@0 | 37 | |
paul@0 | 38 | def __repr__(self): |
paul@0 | 39 | return "Reference(%d, %d, %f)" % (self.degrees, self.minutes, self.seconds) |
paul@0 | 40 | |
paul@0 | 41 | def __str__(self): |
paul@0 | 42 | return "%d:%d:%f" % (self.degrees, self.minutes, self.seconds) |
paul@0 | 43 | |
paul@0 | 44 | def __add__(self, other): |
paul@0 | 45 | if not isinstance(other, Reference): |
paul@0 | 46 | return NotImplemented |
paul@0 | 47 | else: |
paul@0 | 48 | s = sign(self.degrees) |
paul@0 | 49 | o = sign(other.degrees) |
paul@0 | 50 | carry, seconds = adc(s * self.seconds, o * other.seconds) |
paul@0 | 51 | carry, minutes = adc(s * self.minutes, o * other.minutes + carry) |
paul@0 | 52 | return Reference(self.degrees + other.degrees + carry, minutes, seconds) |
paul@0 | 53 | |
paul@0 | 54 | def __sub__(self, other): |
paul@0 | 55 | if not isinstance(other, Reference): |
paul@0 | 56 | return NotImplemented |
paul@0 | 57 | else: |
paul@0 | 58 | return self.__add__(Reference(-other.degrees, other.minutes, other.seconds)) |
paul@0 | 59 | |
paul@0 | 60 | def _compare(self, op, other): |
paul@0 | 61 | if not isinstance(other, Reference): |
paul@0 | 62 | return NotImplemented |
paul@0 | 63 | else: |
paul@0 | 64 | return op(self.to_degrees(), other.to_degrees()) |
paul@0 | 65 | |
paul@0 | 66 | def __eq__(self, other): |
paul@0 | 67 | return self._compare(operator.eq, other) |
paul@0 | 68 | |
paul@0 | 69 | def __ne__(self, other): |
paul@0 | 70 | return self._compare(operator.ne, other) |
paul@0 | 71 | |
paul@0 | 72 | def __lt__(self, other): |
paul@0 | 73 | return self._compare(operator.lt, other) |
paul@0 | 74 | |
paul@0 | 75 | def __le__(self, other): |
paul@0 | 76 | return self._compare(operator.le, other) |
paul@0 | 77 | |
paul@0 | 78 | def __gt__(self, other): |
paul@0 | 79 | return self._compare(operator.gt, other) |
paul@0 | 80 | |
paul@0 | 81 | def __ge__(self, other): |
paul@0 | 82 | return self._compare(operator.ge, other) |
paul@0 | 83 | |
paul@0 | 84 | def to_degrees(self): |
paul@0 | 85 | return sign(self.degrees) * (abs(self.degrees) + self.minutes / 60.0 + self.seconds / 3600.0) |
paul@0 | 86 | |
paul@0 | 87 | def to_pixels(self, scale): |
paul@0 | 88 | return self.to_degrees() * scale |
paul@0 | 89 | |
paul@0 | 90 | def adc(x, y): |
paul@0 | 91 | result = x + y |
paul@0 | 92 | return divmod(result, 60) |
paul@0 | 93 | |
paul@0 | 94 | def getPositionForReference(latitude, longitude, map_y, map_x, map_x_scale, map_y_scale): |
paul@0 | 95 | return (longitude - map_x).to_pixels(map_x_scale), (latitude - map_y).to_pixels(map_y_scale) |
paul@0 | 96 | |
paul@0 | 97 | def getPositionForCentrePoint(position, map_x_scale, map_y_scale): |
paul@0 | 98 | x, y = position |
paul@0 | 99 | return x - map_x_scale / 2.0, y - map_y_scale / 2.0 |
paul@0 | 100 | |
paul@0 | 101 | def getMapReference(value): |
paul@0 | 102 | |
paul@0 | 103 | "Return a map reference by parsing the given 'value'." |
paul@0 | 104 | |
paul@0 | 105 | if value.find(":") != -1: |
paul@0 | 106 | return getMapReferenceFromDMS(value) |
paul@0 | 107 | else: |
paul@0 | 108 | return getMapReferenceFromDecimal(value) |
paul@0 | 109 | |
paul@0 | 110 | def getMapReferenceFromDMS(value): |
paul@0 | 111 | |
paul@0 | 112 | """ |
paul@0 | 113 | Return a map reference by parsing the given 'value' expressed as degrees, |
paul@0 | 114 | minutes, seconds. |
paul@0 | 115 | """ |
paul@0 | 116 | |
paul@0 | 117 | values = value.split(":") |
paul@0 | 118 | values = map(int, values[:2]) + map(float, values[2:3]) |
paul@0 | 119 | return Reference(*values) |
paul@0 | 120 | |
paul@0 | 121 | def getMapReferenceFromDecimal(value): |
paul@0 | 122 | |
paul@0 | 123 | "Return a map reference by parsing the given 'value' in decimal degrees." |
paul@0 | 124 | |
paul@0 | 125 | value = float(value) |
paul@0 | 126 | degrees, remainder = divmod(abs(value * 3600), 3600) |
paul@0 | 127 | minutes, seconds = divmod(remainder, 60) |
paul@0 | 128 | return Reference(sign(value) * degrees, minutes, seconds) |
paul@0 | 129 | |
paul@0 | 130 | # User interface functions. |
paul@0 | 131 | |
paul@0 | 132 | def getNormalisedLocation(location): |
paul@0 | 133 | |
paul@0 | 134 | """ |
paul@0 | 135 | Attempt to return a normalised 'location' of the form "<town>, <country>" or |
paul@0 | 136 | "<town>". |
paul@0 | 137 | """ |
paul@0 | 138 | |
paul@0 | 139 | match = location_normalised_regexp.search(location) |
paul@0 | 140 | if match: |
paul@0 | 141 | return match.group("location") |
paul@0 | 142 | else: |
paul@0 | 143 | return None |
paul@0 | 144 | |
paul@0 | 145 | # vim: tabstop=4 expandtab shiftwidth=4 |