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