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