Changeset 458

Show
Ignore:
Timestamp:
01/25/08 11:19:57 (10 months ago)
Author:
migurski
Message:

Merged python-composition branch with Aaron's new wscompose work

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/py/ModestMaps/__init__.py

    r361 r458  
    1515import Geo 
    1616import Google, Yahoo, Microsoft 
     17import time 
    1718 
    1819def calculateMapCenter(provider, centerCoord): 
     
    107108        try: 
    108109            imgs = [PIL.Image.open(StringIO.StringIO(urllib.urlopen(url).read())).convert('RGBA') 
    109                     for url in urls] 
    110  
     110                    for url in urls]                 
    111111        except: 
     112                 
    112113            if verbose: 
    113114                print 'Failed', urls, '- attempt no.', attempt, 'in thread', thread.get_ident() 
    114115 
    115116            if attempt < TileRequest.MAX_ATTEMPTS: 
     117                time.sleep(1 * attempt) 
    116118                return self.load(lock, verbose, attempt+1) 
    117119            else: 
     
    206208        return location 
    207209 
     210    # 
     211     
     212    def draw_bbox(self, bbox, zoom=16, verbose=False) : 
     213 
     214        sw = Geo.Location(bbox[0], bbox[1]) 
     215        ne = Geo.Location(bbox[2], bbox[3]) 
     216        nw = Geo.Location(ne.lat, sw.lon) 
     217        se = Geo.Location(sw.lat, ne.lon) 
     218         
     219        TL = self.provider.locationCoordinate(nw).zoomTo(zoom) 
     220 
     221        # 
     222 
     223        tiles = TileQueue() 
     224 
     225        cur_lon = sw.lon 
     226        cur_lat = ne.lat         
     227        max_lon = ne.lon 
     228        max_lat = sw.lat 
     229         
     230        x_off = 0 
     231        y_off = 0 
     232        tile_x = 0 
     233        tile_y = 0 
     234         
     235        tileCoord = TL.copy() 
     236 
     237        while cur_lon < max_lon : 
     238 
     239            y_off = 0 
     240            tile_y = 0 
     241             
     242            while cur_lat > max_lat : 
     243                 
     244                tiles.append(TileRequest(self.provider, tileCoord, Core.Point(x_off, y_off))) 
     245                y_off += self.provider.tileHeight() 
     246                 
     247                tileCoord = tileCoord.down() 
     248                loc = self.provider.coordinateLocation(tileCoord) 
     249                cur_lat = loc.lat 
     250 
     251                tile_y += 1 
     252                 
     253            x_off += self.provider.tileWidth()             
     254            cur_lat = ne.lat 
     255             
     256            tile_x += 1 
     257            tileCoord = TL.copy().right(tile_x) 
     258 
     259            loc = self.provider.coordinateLocation(tileCoord) 
     260            cur_lon = loc.lon 
     261 
     262        width = int(self.provider.tileWidth() * tile_x) 
     263        height = int(self.provider.tileHeight() * tile_y) 
     264 
     265        # Quick, look over there! 
     266 
     267        coord, offset = calculateMapExtent(self.provider, 
     268                                           width, height, 
     269                                           Geo.Location(bbox[0], bbox[1]), 
     270                                           Geo.Location(bbox[2], bbox[3])) 
     271 
     272        self.offset = offset 
     273        self.coordinates = coord 
     274        self.dimensions = Core.Point(width, height) 
     275 
     276        return self.draw() 
     277     
     278    # 
     279     
    208280    def draw(self, verbose=False): 
    209281        """ Draw map out to a PIL.Image and return it. 
     
    211283        coord = self.coordinate.copy() 
    212284        corner = Core.Point(int(self.offset.x + self.dimensions.x/2), int(self.offset.y + self.dimensions.y/2)) 
    213          
     285 
    214286        while corner.x > 0: 
    215287            corner.x -= self.provider.tileWidth() 
     
    219291            corner.y -= self.provider.tileHeight() 
    220292            coord = coord.up() 
    221              
     293         
    222294        tiles = TileQueue() 
    223295         
     
    229301                tileCoord = tileCoord.right() 
    230302            rowCoord = rowCoord.down() 
     303 
     304        return self.render_tiles(tiles, self.dimensions.x, self.dimensions.y) 
     305 
     306    # 
     307     
     308    def render_tiles (self, tiles, img_width, img_height, verbose=False) : 
    231309         
    232310        lock = thread.allocate_lock() 
    233      
     311         
    234312        for tile in tiles: 
    235313            # request all needed images 
     
    243321            time.sleep(1) 
    244322     
    245         mapImg = PIL.Image.new('RGB', (self.dimensions.x, self.dimensions.y)) 
     323        mapImg = PIL.Image.new('RGB', (img_width, img_height)) 
    246324         
    247325        for tile in tiles: 
     
    252330                # something failed to paste, so we ignore it 
    253331                pass 
    254          
     332 
    255333        return mapImg 
    256334 
  • trunk/py/ws-compose.py

    r398 r458  
    1 # -*-python-*- 
     1import wscompose 
    22 
    3 
    4 # $Id$ 
    5 
    6  
    7 import ModestMaps 
    8 import StringIO 
    9 import sys 
    10  
    11 from urlparse import urlparse 
    12 from cgi import parse_qs, escape 
    13 import signal, thread, threading, time, sys 
    14 import BaseHTTPServer, SocketServer, mimetypes 
    15 import re 
    16 import tempfile 
    17 import textwrap 
    18 import string 
    19 import base64 
    20 import Image 
    21  
    22 # ############################################################## 
    23  
    24 global done, server 
    25  
    26 done = False 
    27 server = None 
    28  
    29 # ############################################################## 
    30  
    31 class WebServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): 
    32     pass 
    33  
    34 # ############################################################## 
    35  
    36 class WebRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): 
    37  
    38     # ########################################################## 
    39      
    40     def __init__ (self, request, client_address, server) : 
    41         self.__mrk = None 
    42         BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request, client_address, server) 
    43           
    44     # ########################################################## 
    45      
    46     def do_GET(self): 
    47  
    48         scheme, host, path, params, query, hash = urlparse(self.path) 
    49         params = parse_qs(query) 
    50  
    51         args = self.validate_params(params) 
    52  
    53         if not args : 
    54             return 
    55  
    56         img, meta = self.draw_map(args) 
    57  
    58         if not img : 
    59             return 
    60  
    61         self.send_map(img, meta) 
    62         return 
    63  
    64     # ########################################################## 
    65      
    66     def draw_map (self, args) : 
    67         try : 
    68  
    69             if args.has_key('bbox') : 
    70                 return self.draw_map_extentified(args) 
    71             else : 
    72                 return self.draw_map_centered(args) 
    73              
    74         except Exception, e : 
    75             self.error(200, "composer error : %s" % e) 
    76             return False 
    77  
    78     # ########################################################## 
    79  
    80     def draw_map_extentified (self, args) : 
    81  
    82         provider = self.load_provider(args['provider']) 
    83          
    84         coord, offset = ModestMaps.calculateMapExtent(provider, 
    85                                                       args['width'], args['height'], 
    86                                                       ModestMaps.Geo.Location(args['bbox'][0], args['bbox'][1]), 
    87                                                       ModestMaps.Geo.Location(args['bbox'][2], args['bbox'][3])) 
    88  
    89         dim = ModestMaps.Core.Point(args['width'], args['height']) 
    90         map = ModestMaps.Map(provider, dim, coord, offset)             
    91  
    92         img = map.draw() 
    93         meta = None 
    94          
    95         if args.has_key('filter') : 
    96             img = self.apply_filtering(img, args['filter']) 
    97  
    98         #if args.has_key('markers') : 
    99         #    meta = self.add_markers(map, img, args) 
    100  
    101         return (img, meta) 
    102      
    103     # ########################################################## 
    104      
    105     def draw_map_centered (self, args) : 
    106          
    107         provider = self.load_provider(args['provider']) 
    108         loc = ModestMaps.Geo.Location(args['latitude'], args['longitude']) 
    109          
    110         # Migurski : "coordinate.zoomTo() returns a copy, 
    111         # rather than modifying the coordinate in-place." 
    112          
    113         coordinate = provider.locationCoordinate(loc)             
    114         coordinate = coordinate.zoomTo(args['zoom']) 
    115          
    116         coord, offset = ModestMaps.calculateMapCenter(provider, coordinate) 
    117         dim = ModestMaps.Core.Point(args['width'], args['height']) 
    118          
    119         map = ModestMaps.Map(provider, dim, coord, offset)             
    120  
    121         img = map.draw() 
    122         meta = None 
    123          
    124         if args.has_key('filter') : 
    125             img = self.apply_filtering(img, args['filter']) 
    126  
    127         #if args.has_key('marker') :             
    128         #    self.add_marker(img, args) 
    129                  
    130         return (img, meta) 
    131              
    132     # ########################################################## 
    133  
    134     def pinwin(self) : 
    135  
    136         return """iVBORw0KGgoAAAANSUhEUgAAAJ8AAACSCAYAAABbhRg+AAAACXBIWXMAAAsTAAALEwEAmpwYAAAK 
    137 T2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AU 
    138 kSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXX 
    139 Pues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgAB 
    140 eNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAt 
    141 AGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3 
    142 AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dX 
    143 Lh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+ 
    144 5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk 
    145 5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd 
    146 0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA 
    147 4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzA 
    148 BhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/ph 
    149 CJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5 
    150 h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+ 
    151 Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhM 
    152 WE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQ 
    153 AkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+Io 
    154 UspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdp 
    155 r+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZ 
    156 D5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61Mb 
    157 U2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY 
    158 /R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllir 
    159 SKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79u 
    160 p+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6Vh 
    161 lWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1 
    162 mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lO 
    163 k06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7Ry 
    164 FDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3I 
    165 veRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+B 
    166 Z7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/ 
    167 0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5p 
    168 DoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5q 
    169 PNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIs 
    170 OpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5 
    171 hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQ 
    172 rAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9 
    173 rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1d 
    174 T1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aX 
    175 Dm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7 
    176 vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3S 
    177 PVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKa 
    178 RptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO 
    179 32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21 
    180 e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfV 
    181 P1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i 
    182 /suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8 
    183 IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAEZ0FNQQAAsY58+1GTAAAAIGNIUk0AAHolAACA 
    184 gwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAz1SURBVHja7J1bbBzVHca/M+s4tnGaNIlR 
    185 KMW11QuIS1FZHooEJa1URISKVKlqVUEr1D6gSqhSpTz0iRceKlXJQx77FFSKQvvSSjQhIoI2IWlC 
    186 YahpHBMhUyc2hGTXu7M7u7bXe5nTh51xjk9mdmfvs+vvi452tbHX4/Fvv//lnDkjpJRQZZrm5heo 
    187 tisejwueBUB48HnQTUxMYHJykmemQ1pcXEQymSSEHnymaUpC1xsItzKAhveE4HVXPN+A4bke1X1N 
    188 TExs6Rzb4KeQ7tfzsEtRhI8ifBRF+CjCR1GEjyJ8FEX4KMJHUYSPInwURfgowkdRhI8ifBRF+CjC 
    189 RxE+iiJ8FOGjKMJHET6KInwU4aMowkcRPooifBThoyjCRxE+iiJ8FOGjCB9FET6K8FEU4aMIH0UR 
    190 PorwURThowgfRRE+ivBRFOGjCB9FET6K8FEU4aMIH0X4KIrwUYSPoggfRfgoivBRhI+iCB9F+CiK 
    191 8FH9oCGegt5LCBG5Y5JSEr6twt8AfyBkENCEr7/SH9nXnzAhIBUCCV80FGvGSSIuqTxuPBeupUop 
    192 JeGLhrb1OXyyDngSgKP/P+GLhrY3CZ2MKIQqcI4C3gaEQghB+KKhkTa4TFTg80bFBa3iDqE8AgDD 
    193 bkQ0NiCO5yjgVTTYvP8XDLvR0mibcqyoOF4FQBlASQu3QoWR8PUnfFFxP6nB5bjQlRWnU6Hb1Dwk 
    194 fP2X80Xd8YQCXs3+JeGLhoY70O7oFnyqu0GrbIMG4YuQ+u3voIKkQ+fUGIQvgor1KXh6/04tNrzh 
    195 vXYLgIQvGhJ9eMxSg86rbvVRVuBTFxmwz0f4GoYtyOlKAIrKKCnupzqe9BYXcDEp1Wgx42jgebCt 
    196 K49+rueo4BE+qtnK1nM7z+HWNQCLfuDpb0j4qEYLDD2/08HTw+0GeFJbTUr4qEbAc0KCpzqeX+hm 
    197 q4Vq2vGKAeD5VbdSBlwQQuejmgm1RZ8czxc81Jh5ofNRYStb3fX04kLP8QIdj85H1QLPb5VKvXAb 
    198 2vHofFQY8NSWSlO9vLrOt7i4yFPfA0XovDfjeIG9vLDX/BrxeFwkk0mS0AMlk0k8/PDDT0esyPDL 
    199 8YoI7uVtmreVMrTx3cz56H5b2vX8wNPDbVCOp85eSCFEaOcTHqWmaUoAmJiYwOTkJOnoIHRepImA 
    200 6/mBV9JCbSGgug3VywsFnycPwjDqFqjqH2wQFJFQW6uX54FWUABsqLINw6JoZTci0zSvTk9PT+7e 
    201 vbuj4C0sLHy8f//+X7tpgoGbS5AEqHaBV/EJsyp4ej+vace7JedrUs900pHy+Tzm5+dnCV5HKlvU 
    202 CLcqgIHTZmqOp49uwHc+n8+ny+VyR86SZVm4fPnyaYLXsZZK0GLQda3K9c3xpJQIGh2HLx6PVwCc 
    203 tG27/WdJSty4ccM5duzYBYLXEfCcEOD5NpFrOV43nQ8ATudyuY6EXMuyLp09ezat5qhkqGUAa81e 
    204 BIGn9/LqjjBqx/RaR5zPsizMzc29g4Cr3am2VbYlnzCrwufbywsTuTrufPF4fLFYLF5cW1tra8hN 
    205 JBLOyy+/fI7QtQ26oAWhfs7nuyC0Xp7XC+cDgFO2bT8wOjraljfL5XJIpVIfzszMZLsAnxxw+IDa 
    206 C0L9Zi8CK9tGDKQbOR8AvN3OvM+yLMzOzp7xOYGdcoQghxiEoV/IXW9BaMUPvLCO1wvnezubza45 
    207 jjNqGK3xLKVEMpl0jh49eg6b9wARCoCiTW6ggtgp0KMQdvWWyjqCF4T6zl40ujt9GADbAl88Hl8z 
    208 TfPtXC731M6dO1t6L9u2kUql3rt06VJGabEYbah4dbAc1N7IRg4IfPoSqaAFoeUgx+uU2rmY9Gw+ 
    209 n28ZPrfKfcs9EVIBT+31yQYhDNorWCo/xxlAAPVmcqlGVXvLMvhmHK/rzufqeDab/d2dd97Z9Bs4 
    210 joNkMrn+6quvvuOelBg2b6JjNOh+QbuiO1pu47eRzSCAV2txaK0mcmiAIuF88Xj8ommai8VicXJ4 
    211 uLnt5mzbhm3bF0zTXHaPTc3xRBMnHzWgK2uPemthENwvaDZDdTvfXl4rrtcL5/Oq3uf27NnTdMi9 
    212 evXqcfdTKRW383a5DBNua7ldWfsjlH0SbWcAnM/vgxf0ofO9uLsf7712yrbtpuBzHAepVKpw+PDh 
    213 E0qVG9NOTDvczm8LL939+tnxZEBLKSjV8N01tNV7r/XC+U6m0+nK1NRUrNGDz2azWFtbe2tubi6P 
    214 6k1RGsm//CpZPdyo+U69PeTkgMCHGv1LfXNH2Sg8kXK+eDyeNk3z/MrKyqPj4+MNh9xkMvlXnxNR 
    215 L98L43bqjkrqowekX2MVAwIfUH9/5Fu+px+dz2u5NASf4zhIp9OFI0eOnNKA8xth3a6iuVvRx/nK 
    216 CLGV14C4X1ARJZuFJ2o5n9dy+e2+fftCf0Mmk0GxWPz7mTNniqjeFsBQ2iz6QtJa7QR1VW45ALqg 
    217 5UIOBm+GIwyQvupX5/NWN+8eGgr39pZlIZFI/M2FbUgZHoC689W63ZLf/KXudvqlf4MQbhsNxS3D 
    218 06ravleLu7r5dD6fD/X1lUoFlmXZhw4dOqtAt62O86k3llOLCe+ClwKANeW5PpepV7gyaqNVtVo4 
    219 1Vup3I6VzJ3aq+WUbds/3LVrV6iQWygUTp47d66M6m2gtmnOp0+ryRBu51dY6A1VBx2cSmvVOdp4 
    220 m/meHH8v4TuezWZDh9ylpaXXXegaBS9omZCe25V9wiw6GWq7kTN18ud34/hFpwg3TfPSfffdd+/I 
    221 SPBtxcrlMmZmZqwDBw58a3l5edgtNkbcPt8wNs/t+s1TlmtAV6uo6HhhQefrnfMB1QuLasKXyWSw 
    222 urr6xvLysqE5n+p6fh36ei2UUi/cjs7X+2pXDb2/mpiYqBly5+fnj/tUuIYGil+YLcJ/hUa9uVrZ 
    223 D85B52tNNVc3l8tlZDKZzMGDB98L6XgeXH7QBc3R9mx9Hp2ve9dw+LVc1gD8e2VlJdD1crnc65Zl 
    224 iQDwHGxeeau2UfQWStBS8J7N1TZ6zYM+utHq6OTx99r5gOo1vY/v2LHDF77Z2dk3tHArtB5eBcEz 
    225 FUFFRSRWI9P5euh8AFAqlU74tVxKpRKy2ez1F198ccYnx9MvdCkEjHX4LwGPxMoUOh96C98jjzxy 
    226 cW1t7XqpVLrF9TKZzJsumH7g6aFWna0IswS859NkrcLTjT9+J4+/62FX+6Gi8J2jTywsrGZy07l9 
    227 6h5+lmVh/eTHn/rkeDIg1KpDndmQ3W6hsNqNeLUr978y5Uj5AoDn9/wrPZ6NZ+HBVywWsbqUwtfO 
    228 rvzm+AMHv/KnG+deO5Y4/yk2z9XqfbyuT48x5+vDPp8L3ksAnpWQGP/IxuJyClNTUxBCwLIsDP8n 
    229 CQPG6NTI3md+vu/ROxw4R/6cePcT1F/m3pOGMZ2vM+pEzveCEHi2SoaEsVrByOcFeBsJWZaFYTMJ 
    230 IYCYMDA1svd7P5749i/cwmMd/vuH1KtoI6etkPNFCj65/5UnADyPTbYkMXbZhm3bKBaLKHyaxvDV 
    231 PGIwMCRiGBZD+Ob4XQdemv7RU27+V6gBnkSfXGOxFardSOV8jpRPCoFxKavQebptzobltlxG319G 
    232 TBgQEBACMGDAEGL43tu+9ASAEwAs+N/noa9WGW+FnK/VY2wvfHAejfmY6diVFXyWsuE4DnZ8kMaQ 
    233 iG0slIoJAzEITG+//R4AXwbwOYBV9Pn2Fcz5ugxfTBh3+55IBxj9JIfi3iJGrxUgPedz4TOEgbtG 
    234 9uxx4buohdi+go7O1yP43FRl8y/h/hufs1HauQ1Dwrj5uhAwIBATBsqiIgDsBTCmgqduk9RrN6Dz 
    235 Rdj51pzi/8Ziww8KAUAKCMjq/mZC4Asf5VHZXi0yPPgMITYelwqpFQDjuLlHi+w34Oh8PYQvVcrP 
    236 jMV2P6i6niGqj9st914dG/C55bz7dR/kr2TVMOv3i9H56HyB+mj12ul9wzt/us2IDXvweSlbTAht 
    237 yymxAWhBFitvpmevA8gBWJd+8ZvON3DO19Y+35P//f3JC/b8P6sH7w6IajsFRjW/23jmhl0BvJZ4 
    238 99qxxPkFAAkA+aAmaz+Jfb4uOx+A/B8+/8cfAUw+tuvuezwI1TCs63hq5sbhpRMLbovlCoA8nW9r 
    239 OF9br14TQsQA3PX9L97/k1/e8fjPnt770D2jxnAsoDipvJa4cO3Q0onFuZXP5gG8A+AtAEsAKsz5 
    240 Bj/nazd8cFsl3wDw2HP7HvvBd3fd+/WHdkztnh6ZGAOAhUJy9f3cQvaN9IfpvyTevQ7gOgDThe9j 
    241 VBvMfRdmqWjAJ9yWyVcBxAHcD+AO97Xt7peuA8i7oXbWhe8T9zVJ+AhfK6FCoLr1xe0uhFPuc+9i 
    242 jpxbXFxxoUugulq5a5tRU4MLn6eYG4bH3eHtFF50XS7vhtlKL3IOqrf6/wBfX9fxU9N0oAAAAABJ 
    243 RU5ErkJggg==""" 
    244  
    245         return pw 
    246  
    247     # ########################################################## 
    248      
    249     def get_marker (self, args) : 
    250  
    251         # sort of pointless in a CGI-context 
    252         # but think of the ponies... 
    253          
    254         if not self.__mrk : 
    255             pw = self.pinwin() 
    256             self.__mrk = Image.open(StringIO.StringIO(base64.decodestring(pw))) 
    257              
    258         return self.__mrk 
    259  
    260     # ########################################################## 
    261  
    262     def apply_filtering (self, img, filter) : 
    263  
    264         return self.apply_atkinson_dithering(img) 
    265      
    266     # ########################################################## 
    267      
    268     # 
    269     # http://mike.teczno.com/notes/atkinson.html 
    270     # 
    271      
    272     def apply_atkinson_dithering(self, img) : 
    273  
    274         img = img.convert('L') 
    275  
    276         threshold = 128*[0] + 128*[255] 
    277  
    278         for y in range(img.size[1]): 
    279             for x in range(img.size[0]): 
    280  
    281                 old = img.getpixel((x, y)) 
    282                 new = threshold[old] 
    283                 err = (old - new) >> 3 # divide by 8 
    284              
    285                 img.putpixel((x, y), new) 
    286          
    287                 for nxy in [(x+1, y), (x+2, y), (x-1, y+1), (x, y+1), (x+1, y+1), (x, y+2)]: 
    288                     try: 
    289                         img.putpixel(nxy, img.getpixel(nxy) + err) 
    290                     except IndexError: 
    291                         pass 
    292  
    293         return img.convert('RGBA') 
    294      
    295     # ########################################################## 
    296      
    297     def add_marker(self, img, args) : 
    298  
    299         (x, y) = img.size 
    300         marker = self.get_marker() 
    301              
    302         if args['provider'] == args['marker'] : 
    303             offset = 75 / 2 
    304             nw = (x / 2) - offset 
    305             se = (y / 2) + offset 
    306             thumb = img.crop((nw, nw, se, se)) 
    307              
    308         else : 
    309             args['height'] = 75 
    310             args['width'] = 75 
    311             args['provider'] = args['marker'] 
    312             args['marker'] = '' 
    313             args['dither'] = 0             
    314             thumb = self.draw_map(args) 
    315  
    316         # 
    317          
    318         marker.paste(thumb, (11, 10)) 
    319          
    320         x = (x / 2) - 28 
    321         y = (y / 2) - 134 
    322  
    323         img.paste(marker, (x, y), marker) 
    324      
    325     # ########################################################## 
    326  
    327     def add_markers (self, mmap, img, args) : 
    328  
    329         # note the mmap-iness of the 'map' object 
    330         # apparently python is too stupid not to 
    331         # confuse it with its own 'map' function... 
    332          
    333         coords = {} 
    334          
    335         for details in args['markers'] : 
    336             res = self.add_marker2(mmap, img, details) 
    337  
    338             label = "Marker-%s" % details['label'] 
    339                  
    340             if res : 
    341                 coords[label] = ",".join(map(str, res)) 
    342             else : 
    343                 coords[label] = "FAIL" 
    344                  
    345         return coords 
    346              
    347     # ########################################################## 
    348      
    349     def add_marker2(self, map, img, args) : 
    350  
    351         try :  
    352             loc = ModestMaps.Geo.Location(args['latitude'], args['longitude']) 
    353             pt = map.locationPoint(loc) 
    354         except Exception, e : 
    355             print "OH NOES %s" % e 
    356             return False 
    357          
    358         marker = self.get_marker(args) 
    359  
    360         if args.has_key('fill') : 
    361             marker = self.fill_marker(img, marker, args) 
    362  
    363         x = int(pt.x) 
    364         y = int(pt.y) 
    365          
    366         mx = x - 28 
    367         my = y - 134 
    368  
    369         img.paste(marker, (mx, my), marker) 
    370         return (x, y, mx, my) 
    371      
    372     # ########################################################## 
    373  
    374     def fill_marker(self, img, marker, args) : 
    375  
    376         if args['provider'] == args['marker'] : 
    377             offset = 75 / 2 
    378             nw = (x / 2) - offset 
    379             se = (y / 2) + offset 
    380             thumb = img.crop((nw, nw, se, se)) 
    381              
    382         else : 
    383             args['height'] = 75 
    384             args['width'] = 75 
    385             args['provider'] = args['marker'] 
    386             args['marker'] = '' 
    387             args['dither'] = 0             
    388             thumb = self.draw_map(args) 
    389  
    390         # 
    391          
    392         marker.paste(thumb, (11, 10)) 
    393         return marker 
    394      
    395     # ########################################################## 
    396      
    397     def send_map (self, img, meta) : 
    398  
    399         # Oh PIL, why don't you have a 'tostringDWIM' method? 
    400  
    401         fh = StringIO.StringIO() 
    402         img.save(fh, "PNG") 
    403          
    404         self.send_response(200, "OK") 
    405         self.send_header("Content-Type", "image/png") 
    406         self.send_header("Content-Length", fh.len)          
    407         self.send_header("X-Image-Height", img.size[1]) 
    408         self.send_header("X-Image-Width", img.size[0])         
    409  
    410         if meta : 
    411             for item in meta.keys() : 
    412                 header = "X-%s" % item 
    413                 self.send_header(header, meta[item]) 
    414                                   
    415         self.end_headers() 
    416  
    417         self.wfile.write(fh.getvalue()) 
    418         return 
    419          
    420  
    421     # ########################################################## 
    422      
    423     def load_provider (self, value) : 
    424  
    425         # Please for this method to be in MM itself... 
    426          
    427         if value == 'MICROSOFT_ROAD': 
    428             return ModestMaps.Microsoft.RoadProvider() 
    429          
    430         elif value == 'MICROSOFT_AERIAL': 
    431             return ModestMaps.Microsoft.AerialProvider() 
    432          
    433         elif value == 'MICROSOFT_HYBRID': 
    434             return ModestMaps.Microsoft.HybridProvider() 
    435          
    436         elif value == 'GOOGLE_ROAD': 
    437             return ModestMaps.Google.RoadProvider() 
    438          
    439         elif value == 'GOOGLE_AERIAL': 
    440             return ModestMaps.Google.AerialProvider() 
    441          
    442         elif value == 'GOOGLE_HYBRID': 
    443             return ModestMaps.Google.HybridProvider() 
    444          
    445         elif value == 'YAHOO_ROAD': 
    446             return ModestMaps.Yahoo.RoadProvider() 
    447          
    448         elif value == 'YAHOO_AERIAL': 
    449             return ModestMaps.Yahoo.AerialProvider() 
    450          
    451         elif value == 'YAHOO_HYBRID': 
    452             return ModestMaps.Yahoo.HybridProvider() 
    453          
    454         else : 
    455             return None 
    456              
    457     # ########################################################## 
    458      
    459     def validate_params (self, params) : 
    460  
    461         if len(params.keys()) == 0 : 
    462             self.help() 
    463             return False 
    464  
    465         # 
    466         # I am a blank canvas 
    467         # 
    468          
    469         valid = {} 
    470  
    471         # 
    472         # Seeking love and affection... 
    473         # 
    474          
    475         re_coord    = re.compile(r"^-?\d+(?:\.\d+)?$") 
    476         re_num      = re.compile(r"^\d+$") 
    477         re_label    = re.compile(r"^(?:[a-z0-9-_]+)$") 
    478         re_provider = re.compile(r"^(GOOGLE|YAHOO|MICROSOFT)_(ROAD|HYBRID|AERIAL)$") 
    479          
    480         # 
    481         # Where am i? 
    482         #  
    483          
    484         if params.has_key('bbox') : 
    485  
    486             bbox = params['bbox'][0].split(",") 
    487  
    488             if len(bbox) != 4 : 
    489                 self.error(101, "Missing or incomplete %s parameter" % 'bbox') 
    490                 return False 
    491  
    492             bbox = map(string.strip, bbox) 
    493              
    494             for pt in bbox : 
    495                 if not re_coord.match(pt) : 
    496                     self.error(102, "Not a valid lat/long : %s" % pt) 
    497                     return False 
    498  
    499             valid['bbox'] = map(float, bbox) 
    500              
    501         else : 
    502          
    503             for p in ('latitude', 'longitude') : 
    504  
    505                 if not params.has_key(p) : 
    506                     self.error(101, "Missing %s parameter" % p) 
    507                     return False 
    508              
    509                 if not re_coord.match(params[p][0]) : 
    510                     self.error(102, "Not a valid lat/long : %s" % p) 
    511                     return False 
    512  
    513                 valid[p] = float(params[p][0]) 
    514  
    515             # 
    516              
    517             if not params.has_key('accuracy') : 
    518                 self.error(101, "Missing %s parameter" % 'accuracy') 
    519                 return False 
    520  
    521             if not re_num.match(params['accuracy'][0]) : 
    522                 self.error(102, "Not a valid number %s" % 'accuracy') 
    523                 return False 
    524  
    525             valid['zoom'] = float(params['accuracy'][0])             
    526              
    527         # 
    528         # dimensions 
    529         # 
    530          
    531         for p in ('height', 'width') : 
    532  
    533             if not params.has_key(p) : 
    534                 self.error(101, "Missing %s parameter" % p) 
    535                 return False 
    536  
    537             if not re_num.match(params[p][0]) : 
    538                 self.error(102, "Not a valid number %s" % p) 
    539                 return False 
    540  
    541             valid[p] = int(params[p][0]) 
    542              
    543         # 
    544         # map provider 
    545         # 
    546          
    547         if not params.has_key('provider') : 
    548             self.error(101, "Missing %s parameter" % p) 
    549             return False 
    550          
    551         if not re_provider.match(params['provider'][0].upper()) : 
    552             self.error(102, "Not a valid provider") 
    553             return False 
    554  
    555         valid['provider'] = params['provider'][0].upper() 
    556          
    557         # 
    558         # markers? 
    559         # 
    560          
    561         #if params.has_key('marker') : 
    562         #    if not re_provider.match(params['marker'][0].upper()) : 
    563         #        self.error(102, "Not a valid marker provider") 
    564         #        return False 
    565         # 
    566         #    valid['marker'] = params['marker'][0] 
    567  
    568         # 
    569         # markers? (the new new) 
    570         # 
    571          
    572         #if params.has_key('markers') : 
    573         # 
    574         #    valid['markers'] = [] 
    575         #     
    576         #    for pos in params['markers'] : 
    577         # 
    578         #        marker_data = {} 
    579         #         
    580         #        details = pos.split(",") 
    581         #        details = map(string.strip, details) 
    582         # 
    583         #        if len(details) < 3 : 
    584         #            self.error(101, "Missing or incomplete %s parameter : %s" % ('marker', pos)) 
    585         #            return False 
    586         # 
    587         #        if not re_label.match(details[0]) : 
    588         #            self.error(102, "Not a valid marker label : %s" % pos) 
    589         #            return False 
    590         # 
    591         #        marker_data['label'] = unicode(details[0]) 
    592         #         
    593         #        if not re_coord.match(details[1]) : 
    594         #            self.error(102, "Not a valid lat/long : %s" % pos) 
    595         #            return False 
    596         # 
    597         #        marker_data['latitude'] = float(details[1]) 
    598         #         
    599         #        if not re_coord.match(details[2]) : 
    600         #            self.error(102, "Not a valid lat/long : %s" % pos) 
    601         #            return False 
    602         # 
    603         #        marker_data['longitude'] = float(details[2]) 
    604         #         
    605         #        if len(details) > 3 : 
    606         #             
    607         #            if not re_provider.match(details[3].upper()) : 
    608         #                self.error(102, "Not a valid marker provider") 
    609         #                return False 
    610         # 
    611         #            marker_data['fill'] = unicode(details[3]) 
    612         #         
    613         #        valid['markers'].append(marker_data) 
    614          
    615         # 
    616         # filters 
    617         # 
    618  
    619         if params.has_key('filter') and params['filter'][0]: 
    620             valid['filter'] = params['filter'][0] 
    621  
    622         # 
    623         # whoooosh 
    624         # 
    625  
    626         # print valid 
    627         return valid 
    628  
    629     # ########################################################## 
    630  
    631     def help (self) : 
    632          
    633         self.send_response(200, "OK") 
    634         self.send_header("Content-Type", "text/plain") 
    635         self.end_headers() 
    636          
    637         self.wfile.write("ws-compose.py - a bare bone HTTP interface to the ModestMaps map tile composer.\n\n") 
    638  
    639         self.help_header("Example") 
    640         #self.help_para("http://127.0.0.1:9999/?provider=GOOGLE_ROAD&marker=YAHOO_AERIAL&latitude=41.904688&longitude=12.494308&accuracy=17&height=500&width=500") 
    641         self.help_para("http://127.0.0.1:9999/?provider=GOOGLE_ROAD&latitude=41.904688&longitude=12.494308&accuracy=17&height=500&width=500") 
    642         self.help_para("Returns a PNG file of a map centered on the Santa Maria della Vittoria, in Rome.") 
    643          
    644         self.help_header("Parameters") 
    645         self.help_option('provider', 'A valid ModestMaps map tile provider.', True) 
    646         #self.help_option('marker', 'A valid ModestMaps map tile provider. Used to overlay a "pinwin" marker over the chosen lat/lon point', False) 
    647         self.help_option('latitude','A valid decimal latitude.', True) 
    648         self.help_option('longitude', 'A valid decimal longitude.', True) 
    649         self.help_option('accuracy', 'The zoom level / accuracy (as defined by ModestMaps rather than any individual tile provider) of the final image.', True) 
    650         self.help_option('height', 'The height of the final image', True) 
    651         self.help_option('width', 'The width of the final image', True) 
    652          
    653         self.help_header("Errors") 
    654         self.help_para("Errors are returned with the HTTP status code 500. Specific error codes and messages are returned both in the message body as XML and in the 'X-ErrorCode' and 'X-ErrorMessage' headers.") 
    655  
    656         self.help_header("Notes") 
    657         self.help_para("Currently, ws-compose only supports 'centered' maps; map images based on their geographic extent (bounding box) are not available at this time") 
    658  
    659         self.help_header("Questions") 
    660         self.help_qa("Is it fast?", "Not really. It is designed, primarily, to be run on the same machine that is calling the interface.")  
    661         self.help_qa("Will it ever be fast?", "Sure. It is on The List (tm) to create a mod_python and/or wsgi version. Patches are welcome.") 
    662         self.help_qa("Can I request map images asynchronously?", "Not yet.") 
    663         self.help_qa("Can I get a pony?", "No.") 
    664  
    665         self.help_header("License") 
    666         self.help_para("Copyright (c) 2007 Aaron Straup Cope. All Rights Reserved. This is free software. You may redistribute it and/or modify it under the same terms the Perl Artistic License.") 
    667  
    668     # ########################################################## 
    669  
    670     def help_para(self, text) : 
    671  
    672         self.wfile.write(textwrap.fill(text, 72)) 
    673         self.wfile.write("\n\n") 
    674          
    675     def help_header(self, title) : 
    676         ln = "-" * 72 
    677         self.wfile.write("%s\n" % ln); 
    678         self.wfile.write("%s\n" % title.upper()) 
    679         self.wfile.write("%s\n\n" % ln); 
    680          
    681     def help_option(self, opt, desc, required) : 
    682  
    683         present = "required" 
    684  
    685         if not required : 
    686             present = "optional" 
    687              
    688         self.wfile.write("* %s (%s)\n\n%s\n\n" % (opt, present, textwrap.fill(desc, 72, initial_indent="\t", subsequent_indent="\t"))) 
    689          
    690     def help_qa(self, question, answer) : 
    691         self.wfile.write("%s\n\n" % question) 
    692         self.wfile.write("%s\n\n" % textwrap.fill(answer, 72, initial_indent="\t", subsequent_indent="\t")) 
    693  
    694     # ########################################################## 
    695      
    696     def error (self, err_code=999, err_msg="OH NOES!!! INVISIBLE ERRORZ!!!") : 
    697  
    698         err_code = self.sanitize(err_code) 
    699         err_msg  = self.sanitize(err_msg) 
    700          
    701         self.send_response(500, "Server Error") 
    702         self.send_header("Content-Type", "application/xml")  
    703         self.send_header("X-ErrorCode", err_code) 
    704         self.send_header("X-ErrorMessage", err_msg)          
    705         self.end_headers() 
    706         self.wfile.write("<?xml version=\"1.0\" ?><error code=\"%s\">%s</error>" % (err_code, err_msg)) 
    707  
    708     # ########################################################## 
    709      
    710     def sanitize (self, str) : 
    711         return escape(unicode(str)) 
    712      
    713 # ############################################################## 
    714  
    715 def serve(port):     
    716     signal.signal(signal.SIGINT, terminate) 
    717     thread.start_new_thread(runServer, (port,)) 
    718      
    719     global done 
    720     while not done: 
    721         try: 
    722             time.sleep(0.3) 
    723         except IOError: 
    724             pass 
    725     
    726     global server 
    727     server.server_close() 
    728      
    729 def runServer(port): 
    730  
    731     url = "http://127.0.0.1:%s" % port     
    732     print "ws-compose server running on port %s" % port 
    733     print "documentation and usage is available at %s/\n\n" % url 
    734  
    735     global server 
    736     server = WebServer(("", port), WebRequestHandler) 
    737     server.allow_reuse_address = True 
    738     server.serve_forever() 
    739  
    740 def terminate(sig_num, frame): 
    741     global done 
    742     done = True     
    743  
    744 # ############################################################## 
    745  
    746 if __name__ == "__main__": 
    747  
    748     if len(sys.argv) >= 2 : 
    749         port = int(sys.argv[1]) 
    750     else : 
    751         port = 9999 
    752          
    753     serve(port) 
     3if __name__ == "__main__" :         
     4    app = wscompose.server(wscompose.handler) 
     5    app.loop()