From barton at grass.itc.it Sun Apr 1 17:44:07 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Sun Apr 1 17:44:09 2007 Subject: [grass-addons] r391 - trunk/grassaddons/gui/gui_modules Message-ID: <200704011544.l31Fi7L6023316@grass.itc.it> Author: barton Date: 2007-04-01 17:43:58 +0200 (Sun, 01 Apr 2007) New Revision: 391 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py Log: Major update of display system. Using to buffered PseudoDC for better manipulation of canvas images and to implement placeable map decorations like scales and legends. Once tested, I'll clean up commented code. Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-03-30 22:46:57 UTC (rev 390) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-01 15:43:58 UTC (rev 391) @@ -51,10 +51,6 @@ # for cmdlinef cmdfilename = None -ovlchk = {} # track whether decoration overlay item is drawn or not -ovlcoords = {} # positioning coordinates for decoration overlay - - class Command(Thread): """ Creates thread, which will observe the command file and see, if there @@ -190,7 +186,10 @@ self.mapfile = None # image file to be rendered self.img = "" # wx.Image object (self.mapfile) self.ovlist = [] # list of images for overlays - ovlcoords = {} # coordinates for positioning decorative overlays + self.ovlcoords = {} # positioning coordinates for decoration overlay + self.imagedict = {} # images and their ID's for painting and dragging + self.select = {} # selecting/unselecting decorations for dragging + self.ovlchk = {} # showing/hiding decorations # # mouse attributes like currently pressed buttons, position on @@ -223,25 +222,129 @@ self.lastpos = (0,0) - def Draw(self, dc, img=None, dctype='image', coords='0,0'): - """ - Just here as a place holder. - This method should be over-ridden when sub-classed +# def Draw(self, dc, img=None, pdctype='image', coords='0,0'): +# """ +# Just here as a place holder. +# This method should be over-ridden when sub-classed +# """ +# pass +# +# def DrawOvl(self, pdc, type, data, pdctype='image', coords=wx.Rect(0,0,0,0)): +# """ +# Draws map decorations on top of map +# Just here as a place holder. +# This method should be over-ridden when sub-classed +# """ +# pass + +# def Draw(self, dc, img=None, pdctype='image', coords=[0, 0]): +# """ +# Draws image, box and line in the background +# """ +# dc.BeginDrawing() +# dc.SetBackground(wx.Brush(self.GetBackgroundColour())) +# dc.Clear() # make sure you clear the bitmap! +# +# if dctype == 'clear': # erase the display +# dc.EndDrawing() +# return +# bitmap = wx.BitmapFromImage(img) +# dc.DrawBitmap(bitmap, 0, 0, True) # draw the composite map +# +# if dctype == 'box': # draw a box on top of the map +# dc.SetBrush(wx.Brush(wx.CYAN, wx.TRANSPARENT)) +# dc.SetPen(self.pen) +# dc.DrawRectangle(coords[0], coords[1], coords[2], coords[3]) +# elif dctype == 'line': # draw a line on top of the map +# dc.SetBrush(wx.Brush(wx.CYAN, wx.TRANSPARENT)) +# dc.SetPen(self.pen) +# dc.DrawLine(coords[0], coords[1], coords[2], coords[3]) +# +# dc.EndDrawing() + + def Draw(self, pdc, img=None, drawid=None, pdctype='image', coords=[0,0,0,0]): """ - pass + Draws map decorations on top of map + """ + if drawid == None: + if pdctype == 'image' : + drawid = imagedict[img] + elif pdctype == 'clear': + drawid == None + else: + drawid = wx.NewId() + + self.ovlcoords[drawid] = coords + self.ovlchk[drawid] = True + pdc.SetId(drawid) + self.select[drawid] = False + + if pdctype == 'clear': # erase the display + pdc.EndDrawing() + return + + if pdctype == 'image': + bitmap = wx.BitmapFromImage(img) + w,h = bitmap.GetSize() + pdc.DrawBitmap(bitmap, coords[0], coords[1], True) # draw the composite map + pdc.SetIdBounds(drawid, (coords[0],coords[1],w,h)) + + elif pdctype == 'box': # draw a box on top of the map + pdc.SetBrush(wx.Brush(wx.CYAN, wx.TRANSPARENT)) + pdc.SetPen(self.pen) + pdc.DrawRectangle(coords[0], coords[1], coords[2], coords[3]) + pdc.SetIdBounds(drawid,(coords[0], coords[1], coords[2], coords[3])) + self.ovlcoords[drawid] = coords + + elif pdctype == 'line': # draw a line on top of the map + pdc.SetBrush(wx.Brush(wx.CYAN, wx.TRANSPARENT)) + pdc.SetPen(self.pen) + dc.DrawLine(coords[0], coords[1], coords[2], coords[3]) + pdc.SetIdBounds(drawid,(coords[0], coords[1], coords[2], coords[3])) + self.ovlcoords[drawid] = coords + + elif pdctype == 'point': #draw point + pen = self.RandomPen() + pdc.SetPen(pen) + pdc.DrawPoint(coords[0], coords[1]) + pdc.SetIdBounds(drawid,(coords[0], coords[1], coords[2], coords[3])) + self.ovlcoords[drawid] = coords + + elif pdctype == 'text': # draw text on top of map + text = img + w,h = self.GetFullTextExtent(text)[0:2] + pdc.SetFont(self.GetFont()) + pdc.SetTextForeground(self.RandomColor()) + pdc.SetTextBackground(self.RandomColor()) + pdc.DrawText(text, coords[0], coords[1]) + pdc.SetIdBounds(drawid, (coords[0], coords[1], coords[2], coords[3])) + self.ovlcoords[drawid] = coords + + pdc.EndDrawing() + self.Refresh() + def OnPaint(self, event): """ All that is needed here is to draw the buffer to screen """ dc = wx.BufferedPaintDC(self, self._Buffer) - # pseudoDC for map decorations like scale and legend + # use PrepateDC to set position correctly + self.PrepareDC(dc) + # we need to clear the dc BEFORE calling PrepareDC +# bg = wx.TRANSPARENT_BRUSH + bg = wx.Brush(self.GetBackgroundColour()) + dc.SetBackground(bg) + dc.Clear() + # create a clipping rect from our position and size + # and the Update Region rgn = self.GetUpdateRegion() - rgn.Offset(0,0) r = rgn.GetBox() + # draw to the dc using the calculated clipping rect self.pdc.DrawToDCClipped(dc,r) + def OnSize(self, event): """ The Buffer init is done here, to make sure the buffer is always @@ -258,8 +361,8 @@ # get the image to be rendered self.img = self.GetImage() -# self.ovlist = self.GetOverlay() + # update map display if self.img and Map.width + Map.height > 0: # scale image during resize self.img = self.img.Scale(Map.width, Map.height) @@ -288,6 +391,34 @@ """ self._Buffer.SaveFile(FileName, FileType) + def GetOverlay(self): + """ + Converts overlay files to wx.Image + """ + ovlist = [] + if Map.ovlist: + for ovlfile in Map.ovlist: + if os.path.isfile(ovlfile) and os.path.getsize(ovlfile): + img = wx.Image(ovlfile, wx.BITMAP_TYPE_ANY) + img.ConvertAlphaToMask() + ovlist.append(img) + self.imagedict[img] = ovlist.index(img) # set image PeudoDC ID + return ovlist + + + def GetImage(self): + """ + Converts files to wx.Image + """ + if Map.mapfile and os.path.isfile(Map.mapfile) and \ + os.path.getsize(Map.mapfile): + img = wx.Image(Map.mapfile, wx.BITMAP_TYPE_ANY) + else: + img = None + self.imagedict[img] = 99 # set image PeudoDC ID + return img + + def UpdateMap(self, img=None): """ This would get called if the drawing needed to change, for whatever reason. @@ -301,39 +432,32 @@ if DEBUG: print "Buffered Window.UpdateMap(%s): render=%s" % (img, self.render) - if self.render: + # render new map images Map.width, Map.height = self.GetClientSize() self.mapfile = Map.Render(force=self.render) self.img = self.GetImage() self.resize = False - if not self.img: return - self.ovlist = self.GetOverlay() - # redraw decorations on resize event - if self.ovlist != []: - for overlay in self.ovlist: - ovltype = self.ovlist.index(overlay) - if ovltype not in ovlcoords: - ovlcoords[ovltype] = wx.Rect(0,0,0,0) - self.DrawOvl(self.pdc, type=ovltype, data=None, pdctype='clear') - if ovlchk[ovltype] == True: - self.DrawOvl(self.pdc, type=ovltype, data=overlay, pdctype='image', rect=ovlcoords[ovltype]) - dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer) - self.Draw(dc, self.img) - else: - if not self.img: return - self.ovlist = self.GetOverlay() - if self.ovlist != []: - for overlay in self.ovlist: - ovltype = self.ovlist.index(overlay) - if ovltype not in ovlcoords: - ovlcoords[ovltype] = wx.Rect(0,0,0,0) - self.DrawOvl(self.pdc, type=ovltype, data=None, pdctype='clear') - if ovlchk[ovltype] == True: - self.DrawOvl(self.pdc, type=ovltype, data=overlay, pdctype='image', rect=ovlcoords[ovltype]) - dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer) - self.Draw(dc, self.img) + if not self.img: return + id = self.imagedict[self.img] + if not id: return + + # paint images to PseudoDC + self.pdc.Clear() + self.pdc.RemoveAll() + self.Draw(self.pdc, self.img, drawid=id) # draw map image background + self.ovlist = self.GetOverlay() # list of decoration overlay images + if self.ovlist != []: + for img in self.ovlist: + id = self.imagedict[img] + if id not in self.ovlcoords: self.ovlcoords[id] = wx.Rect(0,0,0,0) + if id == None: return # ID has not yet been assigned (image not painted) + if id not in self.ovlchk: self.ovlchk[id] = False + if self.ovlchk[id] == True: # draw any active and defined overlays + self.Draw(self.pdc, img=img, drawid=id, + pdctype='image', coords=self.ovlcoords[id]) + self.resize = False # update statusbar @@ -345,8 +469,9 @@ """ Erase the map display """ - dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer) - self.Draw(dc, dctype='clear') +# dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer) +# self.Draw(dc, dctype='clear') + self.Draw(self.pdc, pdctype='clear') def DragMap(self, moveto): """ @@ -364,75 +489,80 @@ self.dragimg.DoDrawImage(dc, moveto) self.dragimg.EndDrag() - def DragItem(self, lastpos, event): - x,y = lastpos + def DragItem(self, id, event): + x,y = self.lastpos dx = event.GetX() - x dy = event.GetY() - y - r = self.pdc.GetIdBounds(self.dragid) - self.pdc.TranslateId(self.dragid, dx, dy) - dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer) - dc.SetBackground(wx.Brush(self.GetBackgroundColour())) - bitmap = wx.BitmapFromImage(self.img) - dc.Clear() # make sure you clear the bitmap! - dc.DrawBitmap(bitmap, 0, 0, True) # draw the composite map - self.pdc.DrawToDC(dc) - self.RefreshRect(r, True) + r = self.pdc.GetIdBounds(id) + self.pdc.TranslateId(id, dx, dy) + r2 = self.pdc.GetIdBounds(id) + r = r.Union(r2) + r.Inflate(4,4) + self.RefreshRect(r, False) self.lastpos = (event.GetX(),event.GetY()) - ovlcoords[self.dragid] = self.pdc.GetIdBounds(self.dragid) def MouseDraw(self): """ Mouse zoom rectangles and lines """ - img = self.img # composite map in background - dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer) + boxid = wx.ID_NEW if self.mouse['box'] == "box": mousecoords = [self.mouse['begin'][0], self.mouse['begin'][1], \ self.mouse['end'][0] - self.mouse['begin'][0], \ self.mouse['end'][1] - self.mouse['begin'][1]] - self.Draw(dc, img, dctype='box', coords=mousecoords) + r = self.pdc.GetIdBounds(boxid) + r.Inflate(4,4) + self.pdc.ClearId(boxid) + self.RefreshRect(r, False) + self.pdc.SetId(boxid) + self.Draw(self.pdc, drawid=boxid, pdctype='box', coords=mousecoords) elif self.mouse['box'] == "line": mousecoords = [self.mouse['begin'][0], self.mouse['begin'][1], \ self.mouse['end'][0] - self.mouse['begin'][0], \ self.mouse['end'][1] - self.mouse['begin'][1]] - self.Draw(dc, img, dctype='line', coords=mousecoords) + r = self.pdc.GetIdBounds(boxid) + r.Inflate(4,4) + self.pdc.ClearId(boxid) + self.RefreshRect(r, False) + self.pdc.SetId(boxid) + self.Draw(self.pdc, drawid=boxid, pdctype='line', coords=mousecoords) def MouseActions(self, event): """ Mouse motion and button click notifier """ wheel = event.GetWheelRotation() # +- int - # left mouse button pressed - hitradius = 0 # distance for selecting map decorations + hitradius = 5 # distance for selecting map decorations + + # left mouse button pressed if event.LeftDown(): - l = [] - id = '' - # start point of zoom box or drag self.mouse['begin'] = event.GetPositionTuple()[:] - #l = self.pdc.FindObjectsByBBox(x, y) - l = self.pdc.FindObjects(self.mouse['begin'][0], self.mouse['begin'][1], hitradius) - for id in l: - self.pdc.SetIdGreyedOut(id, True) + + # double click to select decoration for dragging + elif event.ButtonDClick(): + # start point of drag + self.lastpos = event.GetPositionTuple()[:] + + # select decoration and get its ID +# l = self.pdc.FindObjectsByBBox(self.mouse['begin'][0], self.mouse['begin'][1]) + idlist = self.pdc.FindObjects(self.lastpos[0], self.lastpos[1], hitradius) + if idlist == []: return + id = idlist[0] + self.select[id] = not self.select[id] + if self.select[id] == True: self.dragid = id - self.lastpos = (event.GetX(),event.GetY()) - break + else: + self.ovlcoords[self.dragid] = self.pdc.GetIdBounds(self.dragid) + self.dragid = None + self.UpdateMap() + id = None # left mouse button released and not just a pointer elif event.LeftUp(): - # dragging map decoration - if self.mouse['box'] == "point" and self.dragid > -1: - self.pdc.SetIdGreyedOut(self.dragid, False) -# -# r = self.pdc.GetIdBounds(self.dragid) -# r.Inflate(4,4) -# self.OffsetRect(r) -# self.RefreshRect(r, True) - self.dragid = -1 - self.UpdateMap() - elif self.mouse['box'] != "point": + if self.mouse['box'] != "point": # end point of zoom box or drag - self.mouse['end'] = event.SetPositionTuple()[:] + self.mouse['end'] = event.GetPositionTuple()[:] # set region in zoom or pan self.Zoom(self.mouse['begin'], self.mouse['end'], self.zoomtype) @@ -448,6 +578,9 @@ # redraw map self.render=True self.UpdateMap() + elif self.dragid: + self.Refresh() + self.Update() elif event.Dragging(): currpos = event.GetPositionTuple()[:] @@ -456,14 +589,17 @@ # dragging or drawing box with left button if self.mouse['box'] == 'drag': self.DragMap(end) + # dragging decoration overlay item + elif self.mouse['box'] == 'point' and self.dragid != None: + self.DragItem(self.dragid, event) + + # dragging something else? else: - if self.dragid != -1: - self.DragItem(self.lastpos,event) self.mouse['end'] = event.GetPositionTuple()[:] self.MouseDraw() - # zoom on mouse wheel + # zoom with mouse wheel elif wheel != 0: # zoom 1/2 of the screen @@ -471,35 +607,21 @@ end = [Map.width - Map.width/4, Map.height - Map.height/4] + elif event.RightDown(): + x,y = event.GetPositionTuple()[:] + #l = self.pdc.FindObjectsByBBox(x, y) + l = self.pdc.FindObjects(x, y, hitradius) + if l: + self.pdc.SetIdGreyedOut(id=l[0], greyout=(not self.pdc.GetIdGreyedOut(id=l[0]))) + self.Refresh() + self.Update() +# r = self.pdc.GetIdBounds(l[0]) +# r.Inflate(4,4) +# self.RefreshRect(r, False) + # store current mouse position self.mouse['pos'] = event.GetPositionTuple()[:] - def GetOverlay(self): - """ - Converts overlay files to wx.Image - """ - ovlist = [] - if Map.ovlist: - for ovlfile in Map.ovlist: - if os.path.isfile(ovlfile) and os.path.getsize(ovlfile): - img = wx.Image(ovlfile, wx.BITMAP_TYPE_ANY) - img.ConvertAlphaToMask() - ovlist.append(img) - - return ovlist - - - def GetImage(self): - """ - Converts files to wx.Image - """ - if Map.mapfile and os.path.isfile(Map.mapfile) and \ - os.path.getsize(Map.mapfile): - img = wx.Image(Map.mapfile, wx.BITMAP_TYPE_ANY) - else: - img = None - return img - def Pixel2Cell(self, x, y): """ Calculates real word coordinates to image coordinates @@ -557,101 +679,106 @@ Map.region['w'] = newreg['w'] - def DrawOvl(self, pdc, type, data, pdctype='image', rect=wx.Rect(0,0,0,0)): - """ - Draws map decorations on top of map - """ - pdc.BeginDrawing() - id = type - pdc.SetId(id)# pdc.SetBackground(wx.Brush(self.GetBackgroundColour())) -# pdc.Clear() # make sure you clear the bitmap! +#class DrawWindow(BufferedWindow): +# """ +# Drawing routine for double buffered drawing. Overwrites Draw method +# in the BufferedWindow class +# """ +# def __init__(self, parent, id = wx.ID_ANY): +# """ +# """ +# ## Any data the Draw() function needs must be initialized before +# ## calling BufferedWindow.__init__, as it will call the Draw +# ## function. +# self.dcmd_list = [] # list of display commands to process +# BufferedWindow.__init__(self, parent, id) +# +# def Draw(self, dc, img=None, pdctype='image', coords=[0, 0]): +# """ +# Draws image, box and line in the background +# """ +# dc.BeginDrawing() +# dc.SetBackground(wx.Brush(self.GetBackgroundColour())) +# dc.Clear() # make sure you clear the bitmap! +# +# if dctype == 'clear': # erase the display +# dc.EndDrawing() +# return +# bitmap = wx.BitmapFromImage(img) +# dc.DrawBitmap(bitmap, 0, 0, True) # draw the composite map +# +# if dctype == 'box': # draw a box on top of the map +# dc.SetBrush(wx.Brush(wx.CYAN, wx.TRANSPARENT)) +# dc.SetPen(self.pen) +# dc.DrawRectangle(coords[0], coords[1], coords[2], coords[3]) +# elif dctype == 'line': # draw a line on top of the map +# dc.SetBrush(wx.Brush(wx.CYAN, wx.TRANSPARENT)) +# dc.SetPen(self.pen) +# dc.DrawLine(coords[0], coords[1], coords[2], coords[3]) +# +# dc.EndDrawing() +# +# def DrawOvl(self, pdc, img=None, id=None, pdctype='image', coords=[0,0,0,0]): +# """ +# Draws map decorations on top of map +# """ +# pdc.BeginDrawing() +# +# if id == None: +# if pdctype == 'image' : +# id = imagedict[img] +# else: +# id = wx.NewId() +# pdc.SetId(id) +# self.select[id] = False +# +# if pdctype == 'clear': # erase the display +## if type > -1: +## pdc.RemoveId(id) +# pdc.EndDrawing() +# return +# +# elif pdctype == 'image': +# bitmap = wx.BitmapFromImage(img) +# w,h = bitmap.GetSize() +# pdc.DrawBitmap(bitmap, coords[0], coords[1], True) # draw the composite map +# pdc.SetIdBounds(id, (coords[0],coords[1],w,h)) +# +# elif pdctype == 'box': # draw a box on top of the map +# pdc.SetBrush(wx.Brush(wx.CYAN, wx.TRANSPARENT)) +# pdc.SetPen(self.pen) +# pdc.DrawRectangle(coords[0], coords[1], coords[2], coords[3]) +# rect.Inflate(pen.GetWidth(),pen.GetWidth()) +# pdc.SetIdBounds(id,(coords[0], coords[1], coords[2], coords[3])) +# +# elif pdctype == 'line': # draw a line on top of the map +# pdc.SetBrush(wx.Brush(wx.CYAN, wx.TRANSPARENT)) +# pdc.SetPen(self.pen) +# dc.DrawLine(rect) +# rect.Inflate(pen.GetWidth(),pen.GetWidth()) +# pdc.SetIdBounds(id,(coords[0], coords[1], coords[2], coords[3])) +# +# elif pdctype == 'point': #draw point +# pen = self.RandomPen() +# pdc.SetPen(pen) +# pdc.DrawPoint(coords[0], coords[1]) +# rect.Inflate(pen.GetWidth(),pen.GetWidth()) +# pdc.SetIdBounds(id,(coords[0], coords[1], coords[2], coords[3])) +# +# elif pdctype == 'text': # draw text on top of map +# text = img +# w,h = self.GetFullTextExtent(text)[0:2] +# pdc.SetFont(self.GetFont()) +# pdc.SetTextForeground(self.RandomColor()) +# pdc.SetTextBackground(self.RandomColor()) +# pdc.DrawText(text, coords[0], coords[1]) +# rect.Inflate(2,2) +# pdc.SetIdBounds(id, (coords[0], coords[1], coords[2], coords[3])) +# +# pdc.EndDrawing() +# self.Refresh() - if pdctype == 'clear': # erase the display - if type > -1: - pdc.ClearId(type) - pdc.EndDrawing() - return - elif pdctype == 'image': - bitmap = wx.BitmapFromImage(data) - w,h = bitmap.GetSize() - pdc.DrawBitmap(bitmap, rect.x, rect.y, True) # draw the composite map - pdc.SetIdBounds(id, (rect[0],rect[1],w,h)) - - elif pdctype == 'box': # draw a box on top of the map - pdc.SetBrush(wx.Brush(wx.CYAN, wx.TRANSPARENT)) - pdc.SetPen(self.pen) - pdc.DrawRectangleRect(rect) - rect.Inflate(pen.GetWidth(),pen.GetWidth()) - pdc.SetIdBounds(id,rect) - - elif pdctype == 'line': # draw a line on top of the map - pdc.SetBrush(wx.Brush(wx.CYAN, wx.TRANSPARENT)) - pdc.SetPen(self.pen) - dc.DrawLine(rect) - rect.Inflate(pen.GetWidth(),pen.GetWidth()) - pdc.SetIdBounds(id,rect) - - elif pdctype == 'point': #draw point - pen = self.RandomPen() - pdc.SetPen(pen) - pdc.DrawPoint(rect.x, rect.y) - rect.Inflate(pen.GetWidth(),pen.GetWidth()) - pdc.SetIdBounds(id,rect) - - elif pdctype == 'text': # draw text on top of map - text = data - w,h = self.GetFullTextExtent(text)[0:2] - pdc.SetFont(self.GetFont()) - pdc.SetTextForeground(self.RandomColor()) - pdc.SetTextBackground(self.RandomColor()) - pdc.DrawText(text, rect.x, rect.y) - rect.Inflate(2,2) - pdc.SetIdBounds(id, rect) - - pdc.EndDrawing() - self.Refresh() - - -class DrawWindow(BufferedWindow): - """ - Drawing routine for double buffered drawing. Overwrites Draw method - in the BufferedWindow class - """ - def __init__(self, parent, id = wx.ID_ANY): - """ - """ - ## Any data the Draw() function needs must be initialized before - ## calling BufferedWindow.__init__, as it will call the Draw - ## function. - self.dcmd_list = [] # list of display commands to process - BufferedWindow.__init__(self, parent, id) - - def Draw(self, dc, img=None, dctype='image', coords=[0, 0]): - """ - Draws image, box and line in the background - """ - dc.BeginDrawing() - dc.SetBackground(wx.Brush(self.GetBackgroundColour())) - dc.Clear() # make sure you clear the bitmap! - - if dctype == 'clear': # erase the display - dc.EndDrawing() - return - bitmap = wx.BitmapFromImage(img) - dc.DrawBitmap(bitmap, 0, 0, True) # draw the composite map - - if dctype == 'box': # draw a box on top of the map - dc.SetBrush(wx.Brush(wx.CYAN, wx.TRANSPARENT)) - dc.SetPen(self.pen) - dc.DrawRectangle(coords[0], coords[1], coords[2], coords[3]) - elif dctype == 'line': # draw a line on top of the map - dc.SetBrush(wx.Brush(wx.CYAN, wx.TRANSPARENT)) - dc.SetPen(self.pen) - dc.DrawLine(coords[0], coords[1], coords[2], coords[3]) - - dc.EndDrawing() - class MapFrame(wx.Frame): """ Main frame for map display window. Drawing takes place in child double buffered @@ -716,20 +843,24 @@ for i in range(len(map_frame_statusbar_fields)): self.statusbar.SetStatusText(map_frame_statusbar_fields[i], i) - # decoration overlays - ovlchk[0] = False - ovlchk[1] = False - Map.addOverlay(type=0, command='d.barscale', l_active=True, l_render=False) # d.barscale overlay - Map.addOverlay(type=1, command='d.barscale', l_active=True, l_render=False) # d.legend overlay + # d.barscale overlay added to rendering overlay list + Map.addOverlay(type=0, command='d.barscale', l_active=True, l_render=False) + # d.barscale overlay added to rendering overlay list as placeholder for d.legend + Map.addOverlay(type=1, command='d.barscale', l_active=True, l_render=False) # # Init map display # self.InitDisplay() # initialize region values - self.MapWindow = DrawWindow(self) # initialize buffered DC +# self.MapWindow = DrawWindow(self) # initialize buffered DC + self.MapWindow = BufferedWindow(self, id = wx.ID_ANY) # initialize buffered DC self.MapWindow.Bind(wx.EVT_MOTION, self.OnMotion) + # decoration overlays + self.ovlchk = self.MapWindow.ovlchk + self.ovlcoords = self.MapWindow.ovlcoords + # # Bind various events # ONLY if we are running from GIS manager @@ -906,16 +1037,71 @@ """ Map.getRegion() Map.getResolution() - self.draw(dc) + self.UpdateMap() +# self.draw(dc) event.Skip() + def OnAlignRegion(self, event): + """ + Align region + """ + if not Map.alignRegion: + Map.alignRegion = True + else: + Map.alignRegion = False + event.Skip() + + def SaveToFile(self, event): + """ + Save to file + """ + dlg = wx.FileDialog(self, "Choose a file name to save the image as a PNG to", + defaultDir = "", + defaultFile = "", + wildcard = "*.png", + style=wx.SAVE) + if dlg.ShowModal() == wx.ID_OK: + self.MapWindow.SaveToFile(dlg.GetPath(), wx.BITMAP_TYPE_PNG) + dlg.Destroy() + + def OnCloseWindow(self, event): + """ + Window closed + """ + Map.Clean() + self.Destroy() + + #close associated controls book page + #get index number of active display + self.disp_idx = track.Track().GetDisp_idx(self) + + # delete associated bookcontrol page if it exists + if self.disp_idx != None: + pg = track.Track().GetCtrls(self.disp_idx, 1) + pg_count = self.ctrlbk.GetPageCount() + pgnum = '0' + if pg_count > 0: + for x in range(0,pg_count): + if self.ctrlbk.GetPage(x) == pg: + pgnum = x + track.Track().popCtrl(self.disp_idx) + self.ctrlbk.DeletePage(pgnum) + + def getRender(self): + """ + returns the current instance of render.Map() + """ + return Map + # toolBar button handlers def onDecoration(self, event): - """Add decorations item menu""" + """ + Decorations overlay menu" + """ point = wx.GetMousePosition() decmenu = wx.Menu() # Add items to the menu - addscale = wx.MenuItem(decmenu, -1,'Add scalebar and north arrow') + addscale = wx.MenuItem(decmenu, -1,'Scalebar and north arrow') bmp = wx.Image(os.path.join(icons,'module-d.barscale.gif'), wx.BITMAP_TYPE_GIF) bmp.Rescale(16, 16) bmp = bmp.ConvertToBitmap() @@ -923,7 +1109,7 @@ decmenu.AppendItem(addscale) self.Bind(wx.EVT_MENU, self.addBarscale, addscale) - addlegend = wx.MenuItem(decmenu, -1,'Add legend') + addlegend = wx.MenuItem(decmenu, -1,'Legend') bmp = wx.Image(os.path.join(icons,'module-d.legend.gif'), wx.BITMAP_TYPE_GIF) bmp.Rescale(16, 16) bmp = bmp.ConvertToBitmap() @@ -937,138 +1123,103 @@ decmenu.Destroy() def addBarscale(self, event): - ovltype = 0 - DecDialog(self, wx.ID_ANY, 'Scale and arrow') + """ + Handler for scale/arrow map decoration menu selection. + """ + ovltype = 0 # index for overlay layer in render + ovlist = self.MapWindow.GetOverlay() + if ovlist == []: return + img = ovlist[0] + id = self.MapWindow.imagedict[img] + if id == None: return + if id not in self.ovlcoords: self.ovlcoords[id] = wx.Rect(0,0,0,0) + # Decoration overlay control dialog dlg = DecDialog(self, wx.ID_ANY, 'Scale and North arrow', size=(350, 200), style=wx.DEFAULT_DIALOG_STYLE, + ovltype=ovltype, cmd='d.barscale', - type=ovltype, - togletxt = "Show/hide scale and arrow", + drawid=id, + checktxt = "Show/hide scale and arrow", ctrltxt = "scale object") dlg.CenterOnScreen() - # this does not return until the dialog is closed. + # If OK button pressed in decoration control dialog val = dlg.ShowModal() - self.MapWindow.UpdateMap() - if ovltype not in ovlcoords: - ovlcoords[ovltype] = wx.Rect(0,0,0,0) - if val == wx.ID_OK: - ovlist = self.MapWindow.GetOverlay() - if ovlist != []: - self.MapWindow.DrawOvl(self.MapWindow.pdc, type=ovltype, data=None, pdctype='clear') - if ovlchk[ovltype] == True: - self.MapWindow.DrawOvl(self.MapWindow.pdc, type=ovltype, - data=ovlist[ovltype], pdctype='image', rect=ovlcoords[ovltype]) + if self.ovlchk[id] == True: + self.MapWindow.Draw(self.MapWindow.pdc, drawid=id, + img=img, pdctype='image', + coords=self.ovlcoords[id]) + self.MapWindow.UpdateMap() dlg.Destroy() def addLegend(self, event): - ovltype = 1 - DecDialog(self, wx.ID_ANY, 'Legend') + """ + Handler for legend map decoration menu selection. + """ + ovltype = 1 # index for overlay layer in render + ovlist = self.MapWindow.GetOverlay() + if ovlist == []: return + img = ovlist[1] + id = self.MapWindow.imagedict[img] + if id == None: return + if id not in self.ovlcoords: self.ovlcoords[id] = wx.Rect(0,0,0,0) + # Decoration overlay control dialog dlg = DecDialog(self, wx.ID_ANY, 'Legend', size=(350, 200), style=wx.DEFAULT_DIALOG_STYLE, + ovltype=ovltype, cmd='d.legend', - type=ovltype, - togletxt = "Show/hide legend", + drawid=id, + checktxt = "Show/hide legend", ctrltxt = "legend object") dlg.CenterOnScreen() - # this does not return until the dialog is closed. + # If OK button pressed in decoration control dialog val = dlg.ShowModal() - self.MapWindow.UpdateMap() - if ovltype not in ovlcoords: - ovlcoords[ovltype] = wx.Rect(0,0,0,0) - if val == wx.ID_OK: - ovlist = self.MapWindow.GetOverlay() - if ovlist != []: - self.MapWindow.DrawOvl(self.MapWindow.pdc, type=ovltype, data=None, pdctype='clear') - if ovlchk[ovltype] == True: - self.MapWindow.DrawOvl(self.MapWindow.pdc, type=ovltype, - data=ovlist[ovltype], pdctype='image', rect=ovlcoords[ovltype]) + if self.ovlchk[id] == True: + self.MapWindow.Draw(self.MapWindow.pdc, drawid=id, + img=img, pdctype='image', + coords=self.ovlcoords[id]) + self.MapWindow.UpdateMap() dlg.Destroy() def getOptData(self, dcmd, type): - - Map.changeOverlay(type=type, command=dcmd, l_active=ovlchk, l_render=False) - - def OnAlignRegion(self, event): """ - Align region + Callback method for decoration overlay command generated by + dialog created in menuform.py """ - if not Map.alignRegion: - Map.alignRegion = True - else: - Map.alignRegion = False - event.Skip() - def SaveToFile(self, event): - """ - Save to file - """ - dlg = wx.FileDialog(self, "Choose a file name to save the image as a PNG to", - defaultDir = "", - defaultFile = "", - wildcard = "*.png", - style=wx.SAVE) - if dlg.ShowModal() == wx.ID_OK: - self.MapWindow.SaveToFile(dlg.GetPath(), wx.BITMAP_TYPE_PNG) - dlg.Destroy() + # Reset comand and rendering options in render.Map. Always render decoration. + # Showing/hiding handled by PseudoDC + Map.changeOverlay(type=type, command=dcmd, l_active=True, l_render=False) - def OnCloseWindow(self, event): - """ - Window closed - """ - Map.Clean() - self.Destroy() - - #close associated controls book page - #get index number of active display - self.disp_idx = track.Track().GetDisp_idx(self) - - # delete associated bookcontrol page if it exists - if self.disp_idx != None: - pg = track.Track().GetCtrls(self.disp_idx, 1) - pg_count = self.ctrlbk.GetPageCount() - pgnum = '0' - if pg_count > 0: - for x in range(0,pg_count): - if self.ctrlbk.GetPage(x) == pg: - pgnum = x - track.Track().popCtrl(self.disp_idx) - self.ctrlbk.DeletePage(pgnum) - - def getRender(self): - """ - returns the current instance of render.Map() - """ - return Map - # end of class MapFrame class DecDialog(wx.Dialog): def __init__(self, parent, id, title, pos=wx.DefaultPosition, size=wx.DefaultSize, - style=wx.DEFAULT_DIALOG_STYLE, cmd=None, type=None, togletxt='', ctrltxt=''): + style=wx.DEFAULT_DIALOG_STYLE, ovltype=0, cmd='d.barscale', drawid=None, checktxt='', ctrltxt=''): wx.Dialog.__init__(self, parent, id, title, pos, size, style) + """ + Controls setting options and displaying/hiding map overlay decorations + """ - self.type = type + self.ovltype = ovltype + self.drawid = drawid self.ovlcmd = cmd - if type > -1 and type in ovlchk: #tracks toggling of decorations - check = ovlchk[type] - else: - check = False + self.ovlchk = self.Parent.MapWindow.ovlchk sizer = wx.BoxSizer(wx.VERTICAL) box = wx.BoxSizer(wx.HORIZONTAL) - self.chkbox = wx.CheckBox(self, wx.ID_ANY, togletxt ) - self.chkbox.SetValue(check) + self.chkbox = wx.CheckBox(self, wx.ID_ANY, checktxt) + if drawid in self.ovlchk: self.chkbox.SetValue(self.ovlchk[drawid]) box.Add(self.chkbox, 0, wx.ALIGN_CENTRE|wx.ALL, 5) sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) @@ -1078,7 +1229,7 @@ sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) box = wx.BoxSizer(wx.HORIZONTAL) - label = wx.StaticText(self, -1, ("Drag %s with mouse in pointer mode to position" % ctrltxt)) + label = wx.StaticText(self, -1, ("Double-click %s with mouse in\npointer mode and drag to position.\nDouble-click again to set" % ctrltxt)) box.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5) sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) @@ -1088,12 +1239,10 @@ btnsizer = wx.StdDialogButtonSizer() btn = wx.Button(self, wx.ID_OK) - btn.SetHelpText("The OK button completes the dialog") btn.SetDefault() btnsizer.AddButton(btn) btn = wx.Button(self, wx.ID_CANCEL) - btn.SetHelpText("The Cancel button cnacels the dialog.") btnsizer.AddButton(btn) btnsizer.Realize() @@ -1102,18 +1251,23 @@ self.SetSizer(sizer) sizer.Fit(self) - self.Bind(wx.EVT_CHECKBOX, self.onToggle, self.chkbox) + self.Bind(wx.EVT_CHECKBOX, self.onCheck, self.chkbox) self.Bind(wx.EVT_BUTTON, self.onOptions, optnbtn) - - def onToggle(self, event): + def onCheck(self, event): + """ + Handler for checkbox for displaying/hiding decoration + """ check = event.IsChecked() - ovlchk[self.type] = check + self.ovlchk[self.drawid] = check def onOptions(self, event): + """ + Sets option for decoration map overlays + """ self.params = [] # parameters to insert into dialog (not working) menuform.GUI().parseCommand(self.ovlcmd, gmpath, - completed=(self.Parent.getOptData,self.type,self.params), + completed=(self.Parent.getOptData,self.ovltype,self.params), parentframe=None) class MapApp(wx.App): From landa at grass.itc.it Mon Apr 2 09:22:52 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Mon Apr 2 09:22:53 2007 Subject: [grass-addons] r392 - trunk/grassaddons/gui/gui_modules Message-ID: <200704020722.l327MqAp032072@grass.itc.it> Author: landa Date: 2007-04-02 09:22:52 +0200 (Mon, 02 Apr 2007) New Revision: 392 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py Log: fix KeyError when resizing the map window Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-01 15:43:58 UTC (rev 391) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-02 07:22:52 UTC (rev 392) @@ -415,6 +415,7 @@ img = wx.Image(Map.mapfile, wx.BITMAP_TYPE_ANY) else: img = None + self.imagedict[img] = 99 # set image PeudoDC ID return img @@ -440,8 +441,10 @@ self.resize = False if not self.img: return - id = self.imagedict[self.img] - if not id: return + try: + id = self.imagedict[self.img] + except: + return # paint images to PseudoDC self.pdc.Clear() From cepicky at grass.itc.it Mon Apr 2 15:20:08 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Mon Apr 2 15:20:10 2007 Subject: [grass-addons] r393 - trunk/grassaddons/gui Message-ID: <200704021320.l32DK8kb003460@grass.itc.it> Author: cepicky Date: 2007-04-02 15:20:08 +0200 (Mon, 02 Apr 2007) New Revision: 393 Modified: trunk/grassaddons/gui/wxgui.py Log: added new button Modified: trunk/grassaddons/gui/wxgui.py =================================================================== --- trunk/grassaddons/gui/wxgui.py 2007-04-02 07:22:52 UTC (rev 392) +++ trunk/grassaddons/gui/wxgui.py 2007-04-02 13:20:08 UTC (rev 393) @@ -318,8 +318,13 @@ ('addgrp', wx.ArtProvider.GetBitmap(wx.ART_FOLDER, wx.ART_TOOLBAR, (16,16)), 'Add layer group', self.addGroup), ('addovl', wx.Bitmap(os.path.join(wxgui_utils.icons,'module-d.grid.gif'), wx.BITMAP_TYPE_ANY), 'Add grid or vector labels overlay', self.onOverlay), ('delcmd', wx.ArtProvider.GetBitmap(wx.ART_DELETE, wx.ART_TOOLBAR, (16,16)), 'Delete selected layer', self.deleteLayer), + ('attributetable',wx.Bitmap(os.path.join(imagepath,'db_open_table.png'),wx.BITMAP_TYPE_ANY), 'Show attribute table', self.ShowAttributeTable), ) + def ShowAttributeTable(self,event): + print self.maptree.GetSelection().GetText() + + def newDisplay(self, event=None): """Create new map display frame""" From cepicky at grass.itc.it Mon Apr 2 15:38:55 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Mon Apr 2 15:38:56 2007 Subject: [grass-addons] r394 - in trunk/grassaddons/gui: . gui_modules Message-ID: <200704021338.l32Dct8c003606@grass.itc.it> Author: cepicky Date: 2007-04-02 15:38:55 +0200 (Mon, 02 Apr 2007) New Revision: 394 Modified: trunk/grassaddons/gui/gui_modules/grassenv.py trunk/grassaddons/gui/gui_modules/wxgui_utils.py trunk/grassaddons/gui/wxgui.py Log: opening vector tables possible Modified: trunk/grassaddons/gui/gui_modules/grassenv.py =================================================================== --- trunk/grassaddons/gui/gui_modules/grassenv.py 2007-04-02 13:20:08 UTC (rev 393) +++ trunk/grassaddons/gui/gui_modules/grassenv.py 2007-04-02 13:38:55 UTC (rev 394) @@ -40,4 +40,6 @@ for line in os.popen("g.gisenv").readlines(): key,val = line.strip().split("=") + val = val.replace("'","") + val = val.replace(";","") env[key] = val Modified: trunk/grassaddons/gui/gui_modules/wxgui_utils.py =================================================================== --- trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-02 13:20:08 UTC (rev 393) +++ trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-02 13:38:55 UTC (rev 394) @@ -330,6 +330,8 @@ def onExpandNode(self, event): self.layer_selected = event.GetItem() + print "###",self.layertype + print "###",self.layer_selected if self.layertype[self.layer_selected] == 'group': self.SetItemImage(self.layer_selected, self.folder_open) Modified: trunk/grassaddons/gui/wxgui.py =================================================================== --- trunk/grassaddons/gui/wxgui.py 2007-04-02 13:20:08 UTC (rev 393) +++ trunk/grassaddons/gui/wxgui.py 2007-04-02 13:38:55 UTC (rev 394) @@ -41,7 +41,7 @@ import gui_modules.render as render import gui_modules.menudata as menudata import gui_modules.menuform as menuform -import gui_modules.grassenv as grassevn +import gui_modules.grassenv as grassenv """Main Python app to set up GIS Manager window and trap commands Only command console is working currently, but windows for @@ -322,9 +322,18 @@ ) def ShowAttributeTable(self,event): - print self.maptree.GetSelection().GetText() + mapsel = self.maptree.GetSelection() + name = mapsel.GetText() + if name.find("@") >-1: + map,mapset = name.strip().split("@") + + print "#%s#%s#%s" % (map,mapset,grassenv.env["MAPSET"]) + if mapset == grassenv.env["MAPSET"]: + from gui_modules import dbm + self.dbmanager = gui_modules.dbm.DBHunter(None, -1,"GRASS Attribute Table Manager: %s" % map,map) + def newDisplay(self, event=None): """Create new map display frame""" From cepicky at grass.itc.it Mon Apr 2 15:40:48 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Mon Apr 2 15:40:50 2007 Subject: [grass-addons] r395 - in trunk/grassaddons/gui: . gui_modules Message-ID: <200704021340.l32Demfj003651@grass.itc.it> Author: cepicky Date: 2007-04-02 15:40:48 +0200 (Mon, 02 Apr 2007) New Revision: 395 Modified: trunk/grassaddons/gui/gui_modules/dbm.py trunk/grassaddons/gui/wxgui.py Log: opening vector tables possible Modified: trunk/grassaddons/gui/gui_modules/dbm.py =================================================================== --- trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-02 13:38:55 UTC (rev 394) +++ trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-02 13:40:48 UTC (rev 395) @@ -157,8 +157,8 @@ #self.sizer.Add(self.sizer2,0,wx.EXPAND) self.SetSizer(self.sizer) - size = wx.DisplaySize() - self.SetSize(size) + #size = (wx + #self.SetSize(size) self.sb = self.CreateStatusBar() self.dbcon = "table: %s; " % self.tablename Modified: trunk/grassaddons/gui/wxgui.py =================================================================== --- trunk/grassaddons/gui/wxgui.py 2007-04-02 13:38:55 UTC (rev 394) +++ trunk/grassaddons/gui/wxgui.py 2007-04-02 13:40:48 UTC (rev 395) @@ -328,7 +328,6 @@ if name.find("@") >-1: map,mapset = name.strip().split("@") - print "#%s#%s#%s" % (map,mapset,grassenv.env["MAPSET"]) if mapset == grassenv.env["MAPSET"]: from gui_modules import dbm self.dbmanager = gui_modules.dbm.DBHunter(None, -1,"GRASS Attribute Table Manager: %s" % map,map) From barton at grass.itc.it Mon Apr 2 17:35:51 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Mon Apr 2 17:35:52 2007 Subject: [grass-addons] r396 - trunk/grassaddons/gui/gui_modules Message-ID: <200704021535.l32FZp8S005001@grass.itc.it> Author: barton Date: 2007-04-02 17:35:39 +0200 (Mon, 02 Apr 2007) New Revision: 396 Modified: trunk/grassaddons/gui/gui_modules/menuform.py trunk/grassaddons/gui/gui_modules/wxgui_utils.py Log: If layer options previously set, these now appear when layer options dialog reopened. No need to start from nothing when resetting options. Modified: trunk/grassaddons/gui/gui_modules/menuform.py =================================================================== --- trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-02 13:40:48 UTC (rev 395) +++ trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-02 15:35:39 UTC (rev 396) @@ -324,18 +324,11 @@ If run standalone, it will allow execution of the command. The command is checked and sent to the clipboard when clicking "Copy". """ - def __init__(self, parent, ID, task_description, get_dcmd=None, layer=None, dcmd_params=None): + def __init__(self, parent, ID, task_description, get_dcmd=None, layer=None): self.get_dcmd = get_dcmd - self.dcmd_params = dcmd_params #this should be passed from the layer tree eventually self.layer = layer self.task = task_description - # inserting existing values from d.* command in layer tree - for p in self.task.params: - if self.dcmd_params != None: - for dparam in self.dcmd_params: - if p == dparam: - p['value'] = self.dcmd_params[dparam] wx.Frame.__init__(self, parent, ID, self.task.name, wx.DefaultPosition, style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL) @@ -420,17 +413,17 @@ self.OnCancel(event) def OnApply(self, event): - cmd = self.createCmd() + cmd,params = self.createCmd() if cmd is not None and self.get_dcmd is not None: # return d.* command to layer tree for rendering - self.get_dcmd(cmd, self.layer) + self.get_dcmd(cmd, self.layer,params) # echo d.* command to output console # self.parent.writeDCommand(cmd) return cmd def OnRun(self, event): - cmd = self.createCmd() + cmd = self.createCmd()[0] if cmd != None and cmd[0:2] != "d.": # Send any non-display command to parent window (probably wxgui.py) @@ -503,12 +496,14 @@ sections = ['Main'] is_section = {} + for task in self.task.params + self.task.flags: if not task.has_key('guisection') or task['guisection']=='': task['guisection'] = 'Options' if not is_section.has_key(task['guisection']): is_section[task['guisection']] = 1 - sections.append( task['guisection'] ) + if task['guisection'] != 'Main': # check for pre-existing parameters passed from layer tree + sections.append( task['guisection'] ) there_is_main = False for i in self.task.params+self.task.flags: if i.has_key('required') and i['required'] == 'yes': @@ -586,10 +581,12 @@ self.cb = wx.ComboBox(which_panel, -1, p['default'], wx.Point(-1, -1), wx.Size(STRING_ENTRY_WIDTH, -1), valuelist, wx.CB_DROPDOWN) + if p['value'] != '': self.cb.SetValue(p['value']) # parameter previously set which_sizer.Add(self.cb, 0, wx.ADJUST_MINSIZE, 5) self.paramdict[self.cb] = ID_PARAM_START + p_count self.cb.Bind( wx.EVT_COMBOBOX, self.EvtComboBox) + # text entry if (p['type'] in ('string','integer','float') and len(p['values']) == 0 and p['gisprompt'] == False @@ -600,6 +597,7 @@ self.txt3 = wx.TextCtrl(which_panel, value = p['default'], size = (STRING_ENTRY_WIDTH, ENTRY_HEIGHT)) + if p['value'] != '': self.txt3.SetValue(p['value']) # parameter previously set which_sizer.Add(self.txt3, 0, wx.ADJUST_MINSIZE| wx.ALL, 5) self.paramdict[self.txt3] = ID_PARAM_START + p_count self.txt3.Bind(wx.EVT_TEXT, self.EvtText) @@ -607,12 +605,15 @@ if p['type'] == 'string' and p['gisprompt'] == True: txt = wx.StaticText(which_panel, label = title + ':') which_sizer.Add(txt, 0, wx.ADJUST_MINSIZE | wx.ALL, 5) + # element selection tree combobox (maps, icons, regions, etc.) if p['prompt'] != 'color': self.selection = select.Select(which_panel, id=wx.ID_ANY, size=(250,-1), type=p['element']) + if p['value'] != '': self.selection.SetValue(p['value']) # parameter previously set which_sizer.Add(self.selection, 0, wx.ADJUST_MINSIZE| wx.ALL, 5) self.paramdict[self.selection] = ID_PARAM_START + p_count self.selection.Bind(wx.EVT_TEXT, self.EvtText) + # color entry elif p['prompt'] == 'color': if p['default'] != '': if p['default'][0] in "0123456789": @@ -629,6 +630,18 @@ else: default_color = (200,200,200) label_color = 'Select Color' + if p['value'] != '': # parameter previously set + if p['value'][0] in "0123456789": + default_color = tuple(map(int,p['value'].split( ':' ))) + label_color = p['value'] + else: + # Convert color names to RGB + try: + default_color = color_str2rgb[ p['value'] ] + label_color = p['value'] + except KeyError: + default_color = (200,200,200) + label_color = 'Select Color' btn_colour = csel.ColourSelect(which_panel, -1, label_color, default_color, wx.DefaultPosition, (150,-1) ) which_sizer.Add(btn_colour, 0, wx.ADJUST_MINSIZE| wx.ALL, 5) self.paramdict[btn_colour] = ID_PARAM_START + p_count @@ -643,6 +656,7 @@ which_panel = self.tab[ f['guisection'] ] title = text_beautify(f['description']) self.chk = wx.CheckBox(which_panel,-1, label = title, style = wx.NO_BORDER) + if 'value' in f: self.chk.SetValue(f['value']) self.chk.SetFont( wx.Font( 12, wx.FONTFAMILY_DEFAULT, wx.NORMAL, text_style, 0, '')) which_sizer.Add(self.chk, 0, wx.EXPAND| wx.ALL, 5) self.paramdict[self.chk] = ID_FLAG_START + f_count @@ -747,6 +761,8 @@ cmd = self.task.name errors = 0 errStr = "" + dcmd_params = {} + for flag in self.task.flags: if 'value' in flag and flag['value']: cmd += ' -' + flag['name'] @@ -760,8 +776,14 @@ if errors and not ignoreErrors: self.OnError(errStr) return None - return cmd + # create paramater dictionary to return to layer tree + dcmd_params['flags'] = self.task.flags + dcmd_params['params'] = self.task.params + + + return cmd,dcmd_params + def getInterfaceDescription( cmd ): """Returns the XML description for the GRASS cmd. @@ -796,12 +818,16 @@ self.parent = parent def parseCommand(self, cmd, gmpath, completed=None, parentframe=-1 ): + + dcmd_params = {} if completed == None: - self.get_dcmd = None + get_dcmd = None layer = None + dcmd_params = None else: - self.get_dcmd = completed[0] + get_dcmd = completed[0] layer = completed[1] + dcmd_params.update(completed[2]) cmdlst = cmd.split(' ') if parentframe != -1: @@ -815,7 +841,11 @@ handler = processTask(self.grass_task) xml.sax.parseString( getInterfaceDescription( cmd ) , handler ) - self.mf = mainFrame(self.parent ,-1, self.grass_task, self.get_dcmd, layer) + # if layer parameters previously set, re-insert them into dialog + if 'params' in dcmd_params: self.grass_task.params = dcmd_params['params'] + if 'flags' in dcmd_params: self.grass_task.flags = dcmd_params['flags'] + + self.mf = mainFrame(self.parent ,-1, self.grass_task, get_dcmd, layer) self.mf.Show(True) if __name__ == "__main__": Modified: trunk/grassaddons/gui/gui_modules/wxgui_utils.py =================================================================== --- trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-02 13:40:48 UTC (rev 395) +++ trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-02 15:35:39 UTC (rev 396) @@ -53,12 +53,12 @@ self.saveitem = {} # dictionary to preserve layer attributes for drag and drop self.first = True # indicates if a layer is just added or not self.drag = False # flag to indicate a drag event is in process - self.params = {} # dictionary of existing command parameters + self.params = '' # existing command parameters to pass back to options dialog self.Map = disp.getRender() self.root = self.AddRoot("Map Layers") - self.SetPyData(self.root, None) + self.SetPyData(self.root, (None,None)) #create image list to use with layer tree il = wx.ImageList(16, 16, False) @@ -176,7 +176,7 @@ self.layerctrl[self.ctrl] = layer # add a data object to hold the layer's command (does not apply to generic command layers) - self.SetPyData(layer, None) + self.SetPyData(layer, (None,None)) #layer is initially unchecked as inactive self.CheckItem(layer, checked=False) @@ -424,7 +424,7 @@ self.reorderLayers() self.drag = False - def getOptData(self, dcmd, layer): + def getOptData(self, dcmd, layer, params): for item in dcmd.split(' '): if 'map=' in item: mapname = item.split('=')[1] @@ -441,7 +441,7 @@ self.SetItemText(layer, mapname) # add command to layer's data - self.SetPyData(layer, dcmd) + self.SetPyData(layer, (dcmd,params)) # check layer as active self.CheckItem(layer, checked=True) @@ -449,6 +449,9 @@ # change parameters for item in layers list in render.Map self.changeLayer(layer) + self.params = params + + def writeDCommand(self, dcmd): # echos d.* command to output console global goutput @@ -490,8 +493,8 @@ self.Map.changeLayer(item=layer, command=cmd, l_active=chk, l_hidden=hidden, l_opacity=opac, l_render=False) else: - if self.GetPyData(layer) != None: - cmd = self.GetPyData(layer) + if self.GetPyData(layer)[0] != None: + cmd = self.GetPyData(layer)[0] opac = float(self.GetItemWindow(layer).GetValue())/100 chk = self.IsItemChecked(layer) hidden = not self.IsVisible(layer) From barton at grass.itc.it Mon Apr 2 17:54:42 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Mon Apr 2 17:54:44 2007 Subject: [grass-addons] r397 - trunk/grassaddons/gui/gui_modules Message-ID: <200704021554.l32FsgB4005254@grass.itc.it> Author: barton Date: 2007-04-02 17:54:33 +0200 (Mon, 02 Apr 2007) New Revision: 397 Modified: trunk/grassaddons/gui/gui_modules/wxgui_utils.py Log: Fix bug so that previously set options parameters associated with layer correctly. Modified: trunk/grassaddons/gui/gui_modules/wxgui_utils.py =================================================================== --- trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-02 15:35:39 UTC (rev 396) +++ trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-02 15:54:33 UTC (rev 397) @@ -53,7 +53,6 @@ self.saveitem = {} # dictionary to preserve layer attributes for drag and drop self.first = True # indicates if a layer is just added or not self.drag = False # flag to indicate a drag event is in process - self.params = '' # existing command parameters to pass back to options dialog self.Map = disp.getRender() @@ -137,6 +136,8 @@ def AddLayer(self, idx, type): self.first = True + params = '' # no initial options parameters + if type == 'command': # generic command layer self.ctrl = wx.TextCtrl(self, id=wx.ID_ANY, value='', @@ -190,47 +191,47 @@ self.SetItemImage(layer, self.rast_icon) self.SetItemText(layer, 'raster (double click to set properties)') # launch the properties dialog - menuform.GUI().parseCommand('d.rast', gmpath, completed=(self.getOptData,layer,self.params), parentframe=self) + menuform.GUI().parseCommand('d.rast', gmpath, completed=(self.getOptData,layer,params), parentframe=self) elif type == 'rgb': self.SetItemImage(layer, self.rgb_icon) self.SetItemText(layer, 'RGB (double click to set properties)') # launch the properties dialog - menuform.GUI().parseCommand('d.rgb', gmpath, completed=(self.getOptData,layer,self.params), parentframe=self) + menuform.GUI().parseCommand('d.rgb', gmpath, completed=(self.getOptData,layer,params), parentframe=self) elif type == 'his': self.SetItemImage(layer, self.his_icon) self.SetItemText(layer, 'HIS (double click to set properties)') # launch the properties dialog - menuform.GUI().parseCommand('d.his', gmpath, completed=(self.getOptData,layer,self.params), parentframe=self) + menuform.GUI().parseCommand('d.his', gmpath, completed=(self.getOptData,layer,params), parentframe=self) elif type == 'rastleg': self.SetItemImage(layer, self.leg_icon) self.SetItemText(layer, 'legend (double click to set properties)') # launch the properties dialog - menuform.GUI().parseCommand('d.legend', gmpath, completed=(self.getOptData,layer,self.params), parentframe=self) + menuform.GUI().parseCommand('d.legend', gmpath, completed=(self.getOptData,layer,params), parentframe=self) elif type == 'vector': self.SetItemImage(layer, self.vect_icon) self.SetItemText(layer, 'vector (double click to set properties)') # launch the properties dialog - menuform.GUI().parseCommand('d.vect', gmpath, completed=(self.getOptData,layer,self.params), parentframe=self) + menuform.GUI().parseCommand('d.vect', gmpath, completed=(self.getOptData,layer,params), parentframe=self) elif type == 'thememap': self.SetItemImage(layer, self.theme_icon) self.SetItemText(layer, 'thematic map (double click to set properties)') # launch the properties dialog - menuform.GUI().parseCommand('d.vect.thematic', gmpath, completed=(self.getOptData,layer,self.params), parentframe=self) + menuform.GUI().parseCommand('d.vect.thematic', gmpath, completed=(self.getOptData,layer,params), parentframe=self) elif type == 'themechart': self.SetItemImage(layer, self.chart_icon) self.SetItemText(layer, 'thematic charts (double click to set properties)') # launch the properties dialog - menuform.GUI().parseCommand('d.vect.chart', gmpath, completed=(self.getOptData,layer,self.params), parentframe=self) + menuform.GUI().parseCommand('d.vect.chart', gmpath, completed=(self.getOptData,layer,params), parentframe=self) elif type == 'grid': self.SetItemImage(layer, self.grid_icon) self.SetItemText(layer, 'grid (double click to set properties)') # launch the properties dialog - menuform.GUI().parseCommand('d.grid', gmpath, completed=(self.getOptData,layer,self.params), parentframe=self) + menuform.GUI().parseCommand('d.grid', gmpath, completed=(self.getOptData,layer,params), parentframe=self) elif type == 'labels': self.SetItemImage(layer, self.labels_icon) self.SetItemText(layer, 'vector labels (double click to set properties)') # launch the properties dialog - menuform.GUI().parseCommand('d.labels', gmpath, completed=(self.getOptData,layer,self.params), parentframe=self) + menuform.GUI().parseCommand('d.labels', gmpath, completed=(self.getOptData,layer,params), parentframe=self) elif type == 'command': self.SetItemImage(layer, self.cmd_icon) elif type == 'group': @@ -245,26 +246,27 @@ layer = event.GetItem() self.layer_selected = layer completed = '' + params = self.GetPyData(layer)[1] # When double clicked or first added, open options dialog if self.layertype[layer] == 'raster': - menuform.GUI().parseCommand('d.rast', gmpath, completed=(self.getOptData,layer,self.params), parentframe=self) + menuform.GUI().parseCommand('d.rast', gmpath, completed=(self.getOptData,layer,params), parentframe=self) elif self.layertype[layer] == 'rgb': - menuform.GUI().parseCommand('d.rgb', gmpath, completed=(self.getOptData,layer,self.params), parentframe=self) + menuform.GUI().parseCommand('d.rgb', gmpath, completed=(self.getOptData,layer,params), parentframe=self) elif self.layertype[layer] == 'his': - menuform.GUI().parseCommand('d.his', gmpath, completed=(self.getOptData,layer,self.params), parentframe=self) + menuform.GUI().parseCommand('d.his', gmpath, completed=(self.getOptData,layer,params), parentframe=self) elif self.layertype[layer] == 'rastleg': - menuform.GUI().parseCommand('d.legend', gmpath, completed=(self.getOptData,layer,self.params), parentframe=self) + menuform.GUI().parseCommand('d.legend', gmpath, completed=(self.getOptData,layer,params), parentframe=self) elif self.layertype[layer] == 'vector': - menuform.GUI().parseCommand('d.vect', gmpath, completed=(self.getOptData,layer,self.params), parentframe=self) + menuform.GUI().parseCommand('d.vect', gmpath, completed=(self.getOptData,layer,params), parentframe=self) elif self.layertype[layer] == 'thememap': - menuform.GUI().parseCommand('d.vect.thematic', gmpath, completed=(self.getOptData,layer,self.params), parentframe=self) + menuform.GUI().parseCommand('d.vect.thematic', gmpath, completed=(self.getOptData,layer,params), parentframe=self) elif self.layertype[layer] == 'themechart': - menuform.GUI().parseCommand('d.vect.chart', gmpath, completed=(self.getOptData,layer,self.params), parentframe=self) + menuform.GUI().parseCommand('d.vect.chart', gmpath, completed=(self.getOptData,layer,params), parentframe=self) elif self.layertype[layer] == 'grid': - menuform.GUI().parseCommand('d.grid', gmpath, completed=(self.getOptData,layer,self.params), parentframe=self) + menuform.GUI().parseCommand('d.grid', gmpath, completed=(self.getOptData,layer,params), parentframe=self) elif self.layertype[layer] == 'labels': - menuform.GUI().parseCommand('d.labels', gmpath, completed=(self.getOptData,layer,self.params), parentframe=self) + menuform.GUI().parseCommand('d.labels', gmpath, completed=(self.getOptData,layer,params), parentframe=self) elif self.layertype[layer] == 'group': if self.IsExpanded(layer): self.Collapse(layer) @@ -449,9 +451,7 @@ # change parameters for item in layers list in render.Map self.changeLayer(layer) - self.params = params - def writeDCommand(self, dcmd): # echos d.* command to output console global goutput From barton at grass.itc.it Mon Apr 2 18:04:09 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Mon Apr 2 18:04:10 2007 Subject: [grass-addons] r398 - trunk/grassaddons/gui/gui_modules Message-ID: <200704021604.l32G49cL005499@grass.itc.it> Author: barton Date: 2007-04-02 18:03:59 +0200 (Mon, 02 Apr 2007) New Revision: 398 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py Log: Decoration parameters now also maintain their previously set options and insert them into the options dialog. Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-02 15:54:33 UTC (rev 397) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-02 16:03:59 UTC (rev 398) @@ -400,7 +400,6 @@ for ovlfile in Map.ovlist: if os.path.isfile(ovlfile) and os.path.getsize(ovlfile): img = wx.Image(ovlfile, wx.BITMAP_TYPE_ANY) - img.ConvertAlphaToMask() ovlist.append(img) self.imagedict[img] = ovlist.index(img) # set image PeudoDC ID return ovlist @@ -415,7 +414,7 @@ img = wx.Image(Map.mapfile, wx.BITMAP_TYPE_ANY) else: img = None - + self.imagedict[img] = 99 # set image PeudoDC ID return img @@ -496,11 +495,13 @@ x,y = self.lastpos dx = event.GetX() - x dy = event.GetY() - y + self.pdc.SetBackground(wx.Brush(self.GetBackgroundColour())) r = self.pdc.GetIdBounds(id) self.pdc.TranslateId(id, dx, dy) - r2 = self.pdc.GetIdBounds(id) - r = r.Union(r2) - r.Inflate(4,4) + if id != 99: + r2 = self.pdc.GetIdBounds(id) + r = r.Union(r2) + r.Inflate(4,4) self.RefreshRect(r, False) self.lastpos = (event.GetX(),event.GetY()) @@ -590,11 +591,12 @@ end = (currpos[0]-self.mouse['begin'][0], \ currpos[1]-self.mouse['begin'][1]) # dragging or drawing box with left button - if self.mouse['box'] == 'drag': + if self.mouse['box'] == 'pan': self.DragMap(end) + self.DragItem(99, event) # dragging decoration overlay item - elif self.mouse['box'] == 'point' and self.dragid != None: + elif self.mouse['box'] == 'point' and self.dragid != None and self.dragid != 99: self.DragItem(self.dragid, event) # dragging something else? @@ -615,12 +617,15 @@ #l = self.pdc.FindObjectsByBBox(x, y) l = self.pdc.FindObjects(x, y, hitradius) if l: - self.pdc.SetIdGreyedOut(id=l[0], greyout=(not self.pdc.GetIdGreyedOut(id=l[0]))) - self.Refresh() - self.Update() -# r = self.pdc.GetIdBounds(l[0]) -# r.Inflate(4,4) -# self.RefreshRect(r, False) + id = l[0] + self.pdc.SetId(id) + if self.pdc.GetIdGreyedOut(id) == True: + self.pdc.SetIdGreyedOut(id, False) + else: + self.pdc.SetIdGreyedOut(id, True) + r = self.pdc.GetIdBounds(id) + r.Inflate(4,4) + self.RefreshRect(r, False) # store current mouse position self.mouse['pos'] = event.GetPositionTuple()[:] @@ -863,7 +868,7 @@ # decoration overlays self.ovlchk = self.MapWindow.ovlchk self.ovlcoords = self.MapWindow.ovlcoords - + self.params = {} # previously set decoration options parameters to insert into options dialog # # Bind various events # ONLY if we are running from GIS manager @@ -1021,7 +1026,7 @@ """ Panning, set mouse to drag """ - self.MapWindow.mouse['box'] = "drag" + self.MapWindow.mouse['box'] = "pan" self.MapWindow.zoomtype = 0 event.Skip() @@ -1130,6 +1135,11 @@ Handler for scale/arrow map decoration menu selection. """ ovltype = 0 # index for overlay layer in render + if ovltype in self.params: + params = self.params[ovltype] + else: + params = '' + ovlist = self.MapWindow.GetOverlay() if ovlist == []: return img = ovlist[0] @@ -1144,7 +1154,8 @@ cmd='d.barscale', drawid=id, checktxt = "Show/hide scale and arrow", - ctrltxt = "scale object") + ctrltxt = "scale object", + params = params) dlg.CenterOnScreen() @@ -1164,6 +1175,11 @@ Handler for legend map decoration menu selection. """ ovltype = 1 # index for overlay layer in render + if ovltype in self.params: + params = self.params[ovltype] + else: + params = '' + ovlist = self.MapWindow.GetOverlay() if ovlist == []: return img = ovlist[1] @@ -1178,7 +1194,8 @@ cmd='d.legend', drawid=id, checktxt = "Show/hide legend", - ctrltxt = "legend object") + ctrltxt = "legend object", + params = params) dlg.CenterOnScreen() @@ -1193,7 +1210,7 @@ self.MapWindow.UpdateMap() dlg.Destroy() - def getOptData(self, dcmd, type): + def getOptData(self, dcmd, type, params): """ Callback method for decoration overlay command generated by dialog created in menuform.py @@ -1202,12 +1219,14 @@ # Reset comand and rendering options in render.Map. Always render decoration. # Showing/hiding handled by PseudoDC Map.changeOverlay(type=type, command=dcmd, l_active=True, l_render=False) + self.params[type] = params # end of class MapFrame class DecDialog(wx.Dialog): def __init__(self, parent, id, title, pos=wx.DefaultPosition, size=wx.DefaultSize, - style=wx.DEFAULT_DIALOG_STYLE, ovltype=0, cmd='d.barscale', drawid=None, checktxt='', ctrltxt=''): + style=wx.DEFAULT_DIALOG_STYLE, ovltype=0, cmd='d.barscale', + drawid=None, checktxt='', ctrltxt='', params=''): wx.Dialog.__init__(self, parent, id, title, pos, size, style) """ Controls setting options and displaying/hiding map overlay decorations @@ -1217,6 +1236,7 @@ self.drawid = drawid self.ovlcmd = cmd self.ovlchk = self.Parent.MapWindow.ovlchk + self.params = params #previously set decoration options to pass back to options dialog sizer = wx.BoxSizer(wx.VERTICAL) @@ -1268,7 +1288,7 @@ """ Sets option for decoration map overlays """ - self.params = [] # parameters to insert into dialog (not working) + menuform.GUI().parseCommand(self.ovlcmd, gmpath, completed=(self.Parent.getOptData,self.ovltype,self.params), parentframe=None) From cepicky at grass.itc.it Mon Apr 2 18:29:10 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Mon Apr 2 18:29:11 2007 Subject: [grass-addons] r399 - trunk/grassaddons/gui/gui_modules Message-ID: <200704021629.l32GTAOJ006089@grass.itc.it> Author: cepicky Date: 2007-04-02 18:29:10 +0200 (Mon, 02 Apr 2007) New Revision: 399 Modified: trunk/grassaddons/gui/gui_modules/wxgui_utils.py Log: small bugfix Modified: trunk/grassaddons/gui/gui_modules/wxgui_utils.py =================================================================== --- trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-02 16:03:59 UTC (rev 398) +++ trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-02 16:29:10 UTC (rev 399) @@ -136,7 +136,7 @@ def AddLayer(self, idx, type): self.first = True - params = '' # no initial options parameters + params = {} # no initial options parameters if type == 'command': # generic command layer From barton at grass.itc.it Mon Apr 2 18:48:15 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Mon Apr 2 18:48:17 2007 Subject: [grass-addons] r400 - trunk/grassaddons/gui/gui_modules Message-ID: <200704021648.l32GmFAi006526@grass.itc.it> Author: barton Date: 2007-04-02 18:48:07 +0200 (Mon, 02 Apr 2007) New Revision: 400 Modified: trunk/grassaddons/gui/gui_modules/dbm.py Log: Bug fix for image not found Modified: trunk/grassaddons/gui/gui_modules/dbm.py =================================================================== --- trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-02 16:29:10 UTC (rev 399) +++ trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-02 16:48:07 UTC (rev 400) @@ -55,7 +55,7 @@ self.InsertColumn(i, column) self.SetColumnWidth(i, 5) i += 1 - lengths.append(1) # + lengths.append(1) # # FIXME: subprocess.Popen should be used @@ -77,7 +77,7 @@ if (j % 2) == 0: self.SetItemBackgroundColour(j, '#e6f1f5') j = j + 1 - + # setting column widths i = 0 for length in lengths: @@ -95,8 +95,8 @@ self.table = MyListCtrl(self, -1, self.tablename) - + self.Bind(wx.EVT_SIZE, self.OnSize) filemenu= wx.Menu() @@ -118,7 +118,7 @@ self.Bind(wx.EVT_MENU, self.OnExit, id=ID_EXIT) tb = self.CreateToolBar( wx.TB_HORIZONTAL | wx.NO_BORDER | wx.TB_FLAT | wx.TB_TEXT) - tb.AddSimpleTool(10, wx.Bitmap('images/db_open_table.png'), 'Open table') + tb.AddSimpleTool(10, wx.Bitmap(os.path.join(imagepath, 'db_open_table.png')), 'Open table') #tb.AddSimpleTool(20, wx.Bitmap('images/up.png'), 'Up one directory') #tb.AddSimpleTool(30, wx.Bitmap('images/home.png'), 'Home') #tb.AddSimpleTool(40, wx.Bitmap('images/refresh.png'), 'Refresh') From barton at grass.itc.it Mon Apr 2 18:49:02 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Mon Apr 2 18:49:04 2007 Subject: [grass-addons] r401 - trunk/grassaddons/gui/images Message-ID: <200704021649.l32Gn2wu006547@grass.itc.it> Author: barton Date: 2007-04-02 18:48:53 +0200 (Mon, 02 Apr 2007) New Revision: 401 Modified: trunk/grassaddons/gui/images/__init__.py Log: update to include new images. I don't know if this is needed or not. Modified: trunk/grassaddons/gui/images/__init__.py =================================================================== --- trunk/grassaddons/gui/images/__init__.py 2007-04-02 16:48:07 UTC (rev 400) +++ trunk/grassaddons/gui/images/__init__.py 2007-04-02 16:48:53 UTC (rev 401) @@ -2,4 +2,7 @@ "grass.map.gif", "grass.smlogo.gif", "grasslogo_big.gif", + "db_open_table.png", + "grass_db.png", + "grass_sql.png", ] From cepicky at grass.itc.it Mon Apr 2 19:47:27 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Mon Apr 2 19:47:28 2007 Subject: [grass-addons] r402 - in trunk/grassaddons/gui: . gui_modules Message-ID: <200704021747.l32HlRvv007247@grass.itc.it> Author: cepicky Date: 2007-04-02 19:47:26 +0200 (Mon, 02 Apr 2007) New Revision: 402 Modified: trunk/grassaddons/gui/gui_modules/dbm.py trunk/grassaddons/gui/gui_modules/menuform.py trunk/grassaddons/gui/wxgui.py Log: new table, sorting possible Modified: trunk/grassaddons/gui/gui_modules/dbm.py =================================================================== --- trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-02 16:48:53 UTC (rev 401) +++ trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-02 17:47:26 UTC (rev 402) @@ -1,195 +1,204 @@ -#!/usr/bin/python """ Database browser for GRASS GIS >= 7 This program is based on FileHunter, publicated in "The wxPython Linux Tutorial" on wxPython WIKI pages. +It also uses some functions from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/426407 + Usage: dbm.py table_name """ -############################################################################ -# -# MODULE: dbm.py -# AUTHOR(S): Jachym Cepicky jachym les-ejk cz -# PURPOSE: Database manager for vector attribute tables stored in -# GRASS GIS -# COPYRIGHT: (C) 2007 by the GRASS Development Team -# -# This program is free software under the GNU General Public -# License (>=v2). Read the file COPYING that comes with GRASS -# for details. -# -############################################################################ +import wx +import wx.lib.mixins.listctrl as listmix -# discussion: -# using database drivers is IMHO impossible -# so, first step: parsing output form db.* commands and using SQL for -# manipulation +import sys,os + +#---------------------------------------------------------------------- +class Log: + r"""\brief Needed by the wxdemos. + The log output is redirected to the status bar of the containing frame. + """ -import wx -import os,sys -import time + def WriteText(self,text_string): + self.write(text_string) -import grassenv -import images -imagepath = images.__path__[0] -sys.path.append(imagepath) + def write(self,text_string): + wx.GetApp().GetTopWindow().SetStatusText(text_string) -ID_BUTTON=100 -ID_EXIT=200 +#---------------------------------------------------------------------- +# The panel you want to test (TestVirtualList) +#---------------------------------------------------------------------- -class MyListCtrl(wx.ListCtrl): - def __init__(self, parent, id, tablename=None): - wx.ListCtrl.__init__(self, parent, id, style=wx.LC_REPORT, ) +class TestVirtualList(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.ColumnSorterMixin): + def __init__(self, parent,log,tablename): + wx.ListCtrl.__init__( self, parent, -1, style=wx.LC_REPORT|wx.LC_VIRTUAL|wx.LC_HRULES|wx.LC_VRULES) + self.log=log + self.tablename = tablename - lengths =[] + #adding some attributes (colourful background for each item rows) + self.attr1 = wx.ListItemAttr() + self.attr1.SetBackgroundColour("light blue") + self.attr2 = wx.ListItemAttr() + self.attr2.SetBackgroundColour("white") + + #building the columns + i = 0 # FIXME: subprocess.Popen should be used # FIXME: Maximal number of columns, when the GUI is still usable - i = 0 for column in os.popen("db.columns table=%s" % - (tablename)).readlines(): + (self.tablename)).readlines(): column = column.strip() self.InsertColumn(i, column) - self.SetColumnWidth(i, 5) + self.SetColumnWidth(i, 50) i += 1 - lengths.append(1) # - + #These two should probably be passed to init more cleanly + #setting the numbers of items = number of elements in the dictionary + self.itemDataMap = {} + self.itemIndexMap = [] # FIXME: subprocess.Popen should be used # FIXME: Max. number of rows, while the GUI is still usable - j = 0 - for line in os.popen("""db.select -c sql="SELECT * FROM %s" """ % tablename): + i = 0 + for line in os.popen("""db.select -c sql="SELECT * FROM %s" """ % self.tablename): attributes = line.strip().split("|") - - k = 0 + self.itemDataMap[i] = [] for attribute in attributes: - if len(attribute) > lengths[k]: - lengths[k] = len(attribute) - if k == 0: - self.InsertStringItem(j, attribute) - else: - self.SetStringItem(j, k, attribute) - k += 1 + self.itemDataMap[i].append(attribute) + self.itemIndexMap.append(i) + i += 1 + self.SetItemCount(len(self.itemDataMap)) + + #mixins + listmix.ListCtrlAutoWidthMixin.__init__(self) + listmix.ColumnSorterMixin.__init__(self, 3) - if (j % 2) == 0: - self.SetItemBackgroundColour(j, '#e6f1f5') - j = j + 1 + #sort by genre (column 2), A->Z ascending order (1) + self.SortListItems(0, 1) - # setting column widths - i = 0 - for length in lengths: - self.SetColumnWidth(i, (length+5)*12) - i += 1 + #events + self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected) + self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated) + self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselected) + self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick) + def OnColClick(self,event): + event.Skip() -class DBHunter(wx.Frame): - def __init__(self, parent, id, title, tablename): - wx.Frame.__init__(self, parent, -1, title) + def OnItemSelected(self, event): + self.currentItem = event.m_itemIndex + self.log.WriteText('OnItemSelected: "%s", "%s"\n' % + (self.currentItem, + self.GetItemText(self.currentItem))) - global imagepath - self.tablename = tablename - self.SetIcon(wx.Icon(os.path.join(imagepath,'grass_db.png'), wx.BITMAP_TYPE_ANY)) + def OnItemActivated(self, event): + self.currentItem = event.m_itemIndex + self.log.WriteText("OnItemActivated: %s\nTopItem: %s\n" % + (self.GetItemText(self.currentItem), self.GetTopItem())) + def getColumnText(self, index, col): + item = self.GetItem(index, col) + return item.GetText() - self.table = MyListCtrl(self, -1, self.tablename) + def OnItemDeselected(self, evt): + self.log.WriteText("OnItemDeselected: %s" % evt.m_itemIndex) - self.Bind(wx.EVT_SIZE, self.OnSize) + #--------------------------------------------------- + # These methods are callbacks for implementing the + # "virtualness" of the list... - filemenu= wx.Menu() - filemenu.Append(ID_EXIT,"E&xit"," Terminate the program") - editmenu = wx.Menu() - netmenu = wx.Menu() - showmenu = wx.Menu() - configmenu = wx.Menu() - helpmenu = wx.Menu() + def OnGetItemText(self, item, col): + index=self.itemIndexMap[item] + s = self.itemDataMap[index][col] + return s - menuBar = wx.MenuBar() - menuBar.Append(filemenu,"&File") - menuBar.Append(editmenu, "&Edit") - menuBar.Append(netmenu, "&Net") - menuBar.Append(showmenu, "&Show") - menuBar.Append(configmenu, "&Config") - menuBar.Append(helpmenu, "&Help") - self.SetMenuBar(menuBar) - self.Bind(wx.EVT_MENU, self.OnExit, id=ID_EXIT) + # def OnGetItemImage(self, item): + # index=self.itemIndexMap[item] + # if ( index % 2) == 0: - tb = self.CreateToolBar( wx.TB_HORIZONTAL | wx.NO_BORDER | wx.TB_FLAT | wx.TB_TEXT) - tb.AddSimpleTool(10, wx.Bitmap(os.path.join(imagepath, 'db_open_table.png')), 'Open table') - #tb.AddSimpleTool(20, wx.Bitmap('images/up.png'), 'Up one directory') - #tb.AddSimpleTool(30, wx.Bitmap('images/home.png'), 'Home') - #tb.AddSimpleTool(40, wx.Bitmap('images/refresh.png'), 'Refresh') - #tb.AddSeparator() - #tb.AddSimpleTool(50, wx.Bitmap('images/write.png'), 'Editor') - #tb.AddSimpleTool(60, wx.Bitmap('images/terminal.png'), 'Terminal') - #tb.AddSeparator() - #tb.AddSimpleTool(70, wx.Bitmap('images/help.png'), 'Help') - tb.Realize() + def OnGetItemAttr(self, item): + index=self.itemIndexMap[item] - #self.sizer2 = wx.BoxSizer(wx.HORIZONTAL) + return self.attr2 + #if ( index % 2) == 0: + # return self.attr2 + #else: + # return self.attr1 - #button1 = wx.Button(self, ID_BUTTON + 1, "F3 View") - #button2 = wx.Button(self, ID_BUTTON + 2, "F4 Edit") - #button3 = wx.Button(self, ID_BUTTON + 3, "F5 Copy") - #button4 = wx.Button(self, ID_BUTTON + 4, "F6 Move") - #button5 = wx.Button(self, ID_BUTTON + 5, "F7 Mkdir") - #button6 = wx.Button(self, ID_BUTTON + 6, "F8 Delete") - #button7 = wx.Button(self, ID_BUTTON + 7, "F9 Rename") - #button8 = wx.Button(self, ID_EXIT, "F10 Quit") + #--------------------------------------------------- + # Matt C, 2006/02/22 + # Here's a better SortItems() method -- + # the ColumnSorterMixin.__ColumnSorter() method already handles the ascending/descending, + # and it knows to sort on another column if the chosen columns have the same value. - # self.sizer2.Add(button1, 1, wx.EXPAND) - # self.sizer2.Add(button2, 1, wx.EXPAND) - # self.sizer2.Add(button3, 1, wx.EXPAND) - # self.sizer2.Add(button4, 1, wx.EXPAND) - # self.sizer2.Add(button5, 1, wx.EXPAND) - # self.sizer2.Add(button6, 1, wx.EXPAND) - # self.sizer2.Add(button7, 1, wx.EXPAND) - # self.sizer2.Add(button8, 1, wx.EXPAND) + def SortItems(self,sorter=cmp): + items = list(self.itemDataMap.keys()) + items.sort(sorter) + self.itemIndexMap = items + + # redraw the list + self.Refresh() - self.Bind(wx.EVT_BUTTON, self.OnExit, id=ID_EXIT) + # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py + def GetListCtrl(self): + return self - self.sizer = wx.BoxSizer(wx.VERTICAL) - #self.sizer.Add(self.splitter,1,wx.EXPAND) - self.sizer.Add(self.table,1, wx.EXPAND | wx.ALL, 3) - #self.sizer.Add(self.sizer2,0,wx.EXPAND) - self.SetSizer(self.sizer) + # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py + #def GetSortImages(self): + # return (self.sm_dn, self.sm_up) - #size = (wx - #self.SetSize(size) + #XXX Looks okay to remove this one (was present in the original demo) + #def getColumnText(self, index, col): + # item = self.GetItem(index, col) + # return item.GetText() - self.sb = self.CreateStatusBar() - self.dbcon = "table: %s; " % self.tablename - for line in os.popen("db.connect -p").readlines(): - self.dbcon += line.strip() +"; " - self.sb.SetStatusText(self.dbcon) - self.Center() - self.Show(True) +#---------------------------------------------------------------------- +# The main window +#---------------------------------------------------------------------- +# This is where you populate the frame with a panel from the demo. +# original line in runTest (in the demo source): +# win = TestPanel(nb, log) +# this is changed to: +# self.win=TestPanel(self,log) +#---------------------------------------------------------------------- +class AttributeManager(wx.Frame): - def OnExit(self,e): - self.Close(True) + def __init__(self, parent, id, title, size, style = wx.DEFAULT_FRAME_STYLE, table=None ): - def OnSize(self, event): - size = self.GetSize() - #self.splitter.SetSashPosition(size.x / 2) - self.sb.SetStatusText(self.dbcon) - event.Skip() + wx.Frame.__init__(self, parent, id, title, size=size, style=style) + self.CreateStatusBar(1) - def OnDoubleClick(self, event): - size = self.GetSize() - #self.splitter.SetSashPosition(size.x / 2) + log=Log() -if __name__ == "__main__": + self.win = TestVirtualList(self, log,tablename=table) + self.Show() - if len(sys.argv) != 2: +def main(argv=None): + if argv is None: + argv = sys.argv + + if len(argv) != 2: print >>sys.stderr, __doc__ sys.exit() - app = wx.App(0) - dbmanager = DBHunter(None, -1, 'GRASS Attribute Table Manager',sys.argv[1]) + # Command line arguments of the script to be run are preserved by the + # hotswap.py wrapper but hotswap.py and its options are removed that + # sys.argv looks as if no wrapper was present. + #print "argv:", `argv` + + #some applications might require image handlers + #wx.InitAllImageHandlers() + + app = wx.PySimpleApp() + f = AttributeManager(None, -1, "GRASS Attribute Table Manager",wx.Size(500,300),table=argv[1]) app.MainLoop() + + +if __name__ == '__main__': + main() Modified: trunk/grassaddons/gui/gui_modules/menuform.py =================================================================== --- trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-02 16:48:53 UTC (rev 401) +++ trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-02 17:47:26 UTC (rev 402) @@ -827,6 +827,7 @@ else: get_dcmd = completed[0] layer = completed[1] + print completed dcmd_params.update(completed[2]) cmdlst = cmd.split(' ') Modified: trunk/grassaddons/gui/wxgui.py =================================================================== --- trunk/grassaddons/gui/wxgui.py 2007-04-02 16:48:53 UTC (rev 401) +++ trunk/grassaddons/gui/wxgui.py 2007-04-02 17:47:26 UTC (rev 402) @@ -330,7 +330,7 @@ if mapset == grassenv.env["MAPSET"]: from gui_modules import dbm - self.dbmanager = gui_modules.dbm.DBHunter(None, -1,"GRASS Attribute Table Manager: %s" % map,map) + self.dbmanager = gui_modules.dbm.AttributeManager(None, -1,"GRASS Attribute Table Manager: %s" % map, size=wx.Size(500,300),table=map) def newDisplay(self, event=None): From barton at grass.itc.it Mon Apr 2 20:00:37 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Mon Apr 2 20:00:37 2007 Subject: [grass-addons] r403 - trunk/grassaddons/gui/gui_modules Message-ID: <200704021800.l32I0bA8008305@grass.itc.it> Author: barton Date: 2007-04-02 20:00:28 +0200 (Mon, 02 Apr 2007) New Revision: 403 Modified: trunk/grassaddons/gui/gui_modules/menuform.py Log: Fix control for multiple entries that are not check boxes. Changed from combobox to a text control. Valid range shown in label rather than in combobox entry. Modified: trunk/grassaddons/gui/gui_modules/menuform.py =================================================================== --- trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-02 17:47:26 UTC (rev 402) +++ trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-02 18:00:28 UTC (rev 403) @@ -576,16 +576,17 @@ v_count += 1 which_sizer.Add( hSizer, 0, wx.ADJUST_MINSIZE, 5) else: - txt = wx.StaticText(which_panel, label = title + ':' ) + txt = wx.StaticText(which_panel, label = title + + '. Valid range=' + str(valuelist).strip("[]'") + ':' ) which_sizer.Add(txt, 0, wx.ADJUST_MINSIZE | wx.ALL, 5) - self.cb = wx.ComboBox(which_panel, -1, p['default'], - wx.Point(-1, -1), wx.Size(STRING_ENTRY_WIDTH, -1), - valuelist, wx.CB_DROPDOWN) - if p['value'] != '': self.cb.SetValue(p['value']) # parameter previously set - which_sizer.Add(self.cb, 0, wx.ADJUST_MINSIZE, 5) - self.paramdict[self.cb] = ID_PARAM_START + p_count - self.cb.Bind( wx.EVT_COMBOBOX, self.EvtComboBox) + self.txt2 = wx.TextCtrl(which_panel, value = p['default'], + size = (STRING_ENTRY_WIDTH, ENTRY_HEIGHT)) + if p['value'] != '': self.txt2.SetValue(p['value']) # parameter previously set + which_sizer.Add(self.txt2, 0, wx.ADJUST_MINSIZE, 5) + self.paramdict[self.txt2] = ID_PARAM_START + p_count + self.txt2.Bind(wx.EVT_TEXT, self.EvtText) + # text entry if (p['type'] in ('string','integer','float') and len(p['values']) == 0 From barton at grass.itc.it Mon Apr 2 21:26:38 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Mon Apr 2 21:26:38 2007 Subject: [grass-addons] r404 - trunk/grassaddons/gui/gui_modules Message-ID: <200704021926.l32JQcPO010677@grass.itc.it> Author: barton Date: 2007-04-02 21:26:32 +0200 (Mon, 02 Apr 2007) New Revision: 404 Modified: trunk/grassaddons/gui/gui_modules/menuform.py Log: Removed debugging text Modified: trunk/grassaddons/gui/gui_modules/menuform.py =================================================================== --- trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-02 18:00:28 UTC (rev 403) +++ trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-02 19:26:32 UTC (rev 404) @@ -828,7 +828,6 @@ else: get_dcmd = completed[0] layer = completed[1] - print completed dcmd_params.update(completed[2]) cmdlst = cmd.split(' ') From barton at grass.itc.it Mon Apr 2 21:39:17 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Mon Apr 2 21:39:18 2007 Subject: [grass-addons] r405 - trunk/grassaddons/gui/gui_modules Message-ID: <200704021939.l32JdHlS011032@grass.itc.it> Author: barton Date: 2007-04-02 21:39:10 +0200 (Mon, 02 Apr 2007) New Revision: 405 Modified: trunk/grassaddons/gui/gui_modules/menuform.py Log: Further fix for multiple entry values. Combobox back for lists of values, text entry for value with range of acceptable, checkboxes for on/off values. Modified: trunk/grassaddons/gui/gui_modules/menuform.py =================================================================== --- trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-02 19:26:32 UTC (rev 404) +++ trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-02 19:39:10 UTC (rev 405) @@ -575,7 +575,7 @@ self.Bind(wx.EVT_CHECKBOX, self.EvtCheckBoxMulti) v_count += 1 which_sizer.Add( hSizer, 0, wx.ADJUST_MINSIZE, 5) - else: + elif len(valuelist) == 1: txt = wx.StaticText(which_panel, label = title + '. Valid range=' + str(valuelist).strip("[]'") + ':' ) which_sizer.Add(txt, 0, wx.ADJUST_MINSIZE | wx.ALL, 5) @@ -586,6 +586,16 @@ which_sizer.Add(self.txt2, 0, wx.ADJUST_MINSIZE, 5) self.paramdict[self.txt2] = ID_PARAM_START + p_count self.txt2.Bind(wx.EVT_TEXT, self.EvtText) + else: + txt = wx.StaticText(which_panel, label = title + ':' ) + which_sizer.Add(txt, 0, wx.ADJUST_MINSIZE | wx.ALL, 5) + self.cb = wx.ComboBox(which_panel, -1, p['default'], + wx.Point(-1, -1), wx.Size(STRING_ENTRY_WIDTH, -1), + valuelist, wx.CB_DROPDOWN) + if p['value'] != '': self.cb.SetValue(p['value']) # parameter previously set + which_sizer.Add(self.cb, 0, wx.ADJUST_MINSIZE, 5) + self.paramdict[self.cb] = ID_PARAM_START + p_count + self.cb.Bind( wx.EVT_COMBOBOX, self.EvtComboBox) # text entry if (p['type'] in ('string','integer','float') From barton at grass.itc.it Mon Apr 2 21:53:51 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Mon Apr 2 21:53:53 2007 Subject: [grass-addons] r406 - trunk/grassaddons/gui/gui_modules Message-ID: <200704021953.l32JrprM011089@grass.itc.it> Author: barton Date: 2007-04-02 21:53:45 +0200 (Mon, 02 Apr 2007) New Revision: 406 Modified: trunk/grassaddons/gui/gui_modules/wxgui_utils.py Log: Fix bug in layer drag and drop. Send only command to render, not parameters. Modified: trunk/grassaddons/gui/gui_modules/wxgui_utils.py =================================================================== --- trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-02 19:39:10 UTC (rev 405) +++ trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-02 19:53:45 UTC (rev 406) @@ -420,7 +420,7 @@ self.Map.addLayer(item=new, command=self.saveitem['windval'], l_active=self.saveitem['check'], l_hidden=False, l_opacity=1, l_render=False) else: - self.Map.addLayer(item=new, command=self.saveitem['data'], l_active=self.saveitem['check'], + self.Map.addLayer(item=new, command=self.saveitem['data'][0], l_active=self.saveitem['check'], l_hidden=False, l_opacity=self.saveitem['windval'], l_render=False) self.reorderLayers() From barton at grass.itc.it Tue Apr 3 04:50:38 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Tue Apr 3 04:50:39 2007 Subject: [grass-addons] r407 - trunk/grassaddons/gui/gui_modules Message-ID: <200704030250.l332ocd1016853@grass.itc.it> Author: barton Date: 2007-04-03 04:50:30 +0200 (Tue, 03 Apr 2007) New Revision: 407 Modified: trunk/grassaddons/gui/gui_modules/render.py Log: Docstring correction Modified: trunk/grassaddons/gui/gui_modules/render.py =================================================================== --- trunk/grassaddons/gui/gui_modules/render.py 2007-04-02 19:53:45 UTC (rev 406) +++ trunk/grassaddons/gui/gui_modules/render.py 2007-04-03 02:50:30 UTC (rev 407) @@ -852,7 +852,7 @@ def delLayer(self, item): """ - Removes layer from list of layers, defined by name@mapset or id + Removes layer from list of layers, defined by layer ID Parameters: name - map name From barton at grass.itc.it Tue Apr 3 04:53:47 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Tue Apr 3 04:53:49 2007 Subject: [grass-addons] r408 - trunk/grassaddons/gui/gui_modules Message-ID: <200704030253.l332rlX5016905@grass.itc.it> Author: barton Date: 2007-04-03 04:53:39 +0200 (Tue, 03 Apr 2007) New Revision: 408 Modified: trunk/grassaddons/gui/gui_modules/wxgui_utils.py Log: Fixing bugs for group layers and in drag and drop. Still an issue with transparency/opacity. Modified: trunk/grassaddons/gui/gui_modules/wxgui_utils.py =================================================================== --- trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-03 02:50:30 UTC (rev 407) +++ trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-03 02:53:39 UTC (rev 408) @@ -34,8 +34,8 @@ id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.SUNKEN_BORDER, ctstyle=CT.TR_HAS_BUTTONS | CT.TR_HAS_VARIABLE_ROW_HEIGHT | - CT.TR_HIDE_ROOT | CT.TR_ROW_LINES | CT.TR_FULL_ROW_HIGHLIGHT, - disp=None, log=None): + CT.TR_HIDE_ROOT | CT.TR_ROW_LINES | CT.TR_FULL_ROW_HIGHLIGHT| + CT.TR_EDIT_LABELS, disp=None, log=None): CT.CustomTreeCtrl.__init__(self, parent, id, pos, size, style,ctstyle) self.SetAutoLayout(True) @@ -164,7 +164,7 @@ '', ct_type=1, wnd=self.ctrl ) elif (self.layer_selected and self.layer_selected != self.GetRootItem() and \ self.layertype[self.layer_selected] == 'group'): - layer = self.InsertItem(self.layer_selected, self.GetPrevSibling(self.layer_selected), + layer = self.InsertItem(self.layer_selected, self.layer_selected, '', ct_type=1, wnd=self.ctrl ) self.Expand(self.layer_selected) else: @@ -183,7 +183,8 @@ self.CheckItem(layer, checked=False) # add layer to layers list in render.Map - self.Map.addLayer(item=layer, command='', l_active=False, + if self.layertype[layer] != 'group': + self.Map.addLayer(item=layer, command='', l_active=False, l_hidden=False, l_opacity=1, l_render=False) # add text and icons for each layer type @@ -275,11 +276,13 @@ def onDeleteLayer(self, event): layer = event.GetItem() - self.layertype.pop(layer) # delete layer in render.Map - self.Map.delLayer(item=layer) + if self.layertype[layer] != 'group': + self.Map.delLayer(item=layer) + self.layertype.pop(layer) + def onLayerChecked(self, event): layer = event.GetItem() checked = layer.IsChecked() @@ -311,6 +314,7 @@ event.Skip() def onOpacity(self, event): + print 'opacity event:', event.GetString() if 'Spin' in str(event.GetEventObject()): layer = self.layerctrl[event.GetEventObject()] else: @@ -332,8 +336,6 @@ def onExpandNode(self, event): self.layer_selected = event.GetItem() - print "###",self.layertype - print "###",self.layer_selected if self.layertype[self.layer_selected] == 'group': self.SetItemImage(self.layer_selected, self.folder_open) @@ -352,10 +354,14 @@ self.saveitem['image'] = self.GetItemImage(self.dragItem, 0) self.saveitem['text'] = self.GetItemText(self.dragItem) self.saveitem['wind'] = self.GetItemWindow(self.dragItem) - self.saveitem['windval'] = self.GetItemWindow(self.dragItem).GetValue() - self.saveitem['data'] = self.GetPyData(self.dragItem) + if self.layertype[self.dragItem] == 'group': + self.saveitem['windval'] = None + self.saveitem['data'] = None + else: + self.saveitem['windval'] = self.GetItemWindow(self.dragItem).GetValue() + self.saveitem['data'] = self.GetPyData(self.dragItem) else: - print ("Cant drag a node that has children") + print ("Can't drag a node that has children") def onEndDrag(self, event): """ @@ -363,69 +369,78 @@ delete original at old position """ - # Make sure this memeber exists. try: old = self.dragItem except: return - # recreate old layer at new position + # recreate spin/text control for layer if self.layertype[old] == 'command': - self.dragctrl = wx.TextCtrl(self, id=wx.ID_ANY, value='', + newctrl = wx.TextCtrl(self, id=wx.ID_ANY, value='', pos=wx.DefaultPosition, size=(250,40), style=wx.TE_MULTILINE|wx.TE_WORDWRAP) - self.dragctrl.Bind(wx.EVT_TEXT_ENTER, self.onCmdChanged) + newctrl.Bind(wx.EVT_TEXT_ENTER, self.onCmdChanged) + elif self.layertype[old] == 'group': + newctrl = None else: - self.dragctrl = wx.SpinCtrl(self, id=wx.ID_ANY, value="", pos=(30, 50), + newctrl = wx.SpinCtrl(self, id=wx.ID_ANY, value="", pos=(30, 50), style=wx.SP_ARROW_KEYS) - self.dragctrl.SetRange(1,100) - self.dragctrl.SetValue(100) - self.dragctrl.Bind(wx.EVT_SPINCTRL, self.onOpacity) + newctrl.SetRange(1,100) + newctrl.SetValue(100) + newctrl.Bind(wx.EVT_TEXT, self.onOpacity) - - #If we dropped somewhere that isn't on top of an item, ignore the event + # Decide where to put new layer and put it there flag = self.HitTest(event.GetPoint())[1] if flag & wx.TREE_HITTEST_ABOVE: new = self.PrependItem(self.root, text=self.saveitem['text'], \ - ct_type=1, wnd=self.dragctrl, image=self.saveitem['image'], \ + ct_type=1, wnd=newctrl, image=self.saveitem['image'], \ data=self.saveitem['data']) elif (flag & wx.TREE_HITTEST_BELOW) or (flag & wx.TREE_HITTEST_NOWHERE): new = self.AppendItem(self.root, text=self.saveitem['text'], \ - ct_type=1, wnd=self.dragctrl, image=self.saveitem['image'], \ + ct_type=1, wnd=newctrl, image=self.saveitem['image'], \ data=self.saveitem['data']) else: if not event.GetItem(): return else: afteritem = event.GetItem() - parent = self.GetItemParent(afteritem) - new = self.InsertItem(parent, afteritem, text=self.saveitem['text'], \ - ct_type=1, wnd=self.dragctrl, image=self.saveitem['image'], \ - data=self.saveitem['data']) + if self.layertype[afteritem] == 'group': + parent = afteritem + new = self.AppendItem(parent, text=self.saveitem['text'], \ + ct_type=1, wnd=newctrl, image=self.saveitem['image'], \ + data=self.saveitem['data']) + else: + parent = self.GetItemParent(afteritem) + new = self.InsertItem(parent, afteritem, text=self.saveitem['text'], \ + ct_type=1, wnd=newctrl, image=self.saveitem['image'], \ + data=self.saveitem['data']) - - - self.layertype[new] = self.saveitem['type'] self.CheckItem(new, checked=self.saveitem['check']) - self.GetItemWindow(new).SetValue(self.saveitem['windval']) + if self.layertype[new] != 'group': + self.layerctrl[newctrl] = new + newctrl.SetValue(self.saveitem['windval']) # delete layer at original position - self.Delete(old) + self.Delete(old) # entry in render.Map layers list automatically deleted by onDeleteLayer handler - # update layers list in render.Map + # Add new layer to layers list in render.Map if self.saveitem['type'] == 'command': self.Map.addLayer(item=new, command=self.saveitem['windval'], l_active=self.saveitem['check'], l_hidden=False, l_opacity=1, l_render=False) - else: + + elif self.saveitem['type'] != 'group': self.Map.addLayer(item=new, command=self.saveitem['data'][0], l_active=self.saveitem['check'], l_hidden=False, l_opacity=self.saveitem['windval'], l_render=False) - self.reorderLayers() + # completed drag and drop self.drag = False + # reorder layers in render.Map to match new order after drag and drop + self.reorderLayers() + def getOptData(self, dcmd, layer, params): for item in dcmd.split(' '): if 'map=' in item: @@ -462,13 +477,12 @@ add commands from data associated with any valid and checked layers to layer list """ - # first empty the list of old layers -# self.Map.Clean() + # make a list of visible layers treelayers = [] vislayer = self.GetFirstVisibleItem() for item in range(0,self.GetCount()): - if self.IsItemChecked(vislayer): + if self.IsItemChecked(vislayer) and self.layertype[vislayer] != 'group': treelayers.append(vislayer) if self.GetNextVisible(vislayer) == None: break @@ -481,7 +495,8 @@ self.Map.changeOpacity(layer, opacity) def changeChecked(self, layer, check): - self.Map.changeActive(layer, check) + if self.layertype[layer] != 'group': + self.Map.changeActive(layer, check) def changeLayer(self, layer): if self.layertype[layer] == 'command': @@ -492,7 +507,7 @@ hidden = not self.IsVisible(layer) self.Map.changeLayer(item=layer, command=cmd, l_active=chk, l_hidden=hidden, l_opacity=opac, l_render=False) - else: + elif self.layertype[layer] != 'group': if self.GetPyData(layer)[0] != None: cmd = self.GetPyData(layer)[0] opac = float(self.GetItemWindow(layer).GetValue())/100 From barton at grass.itc.it Tue Apr 3 06:49:30 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Tue Apr 3 06:49:31 2007 Subject: [grass-addons] r409 - trunk/grassaddons/gui Message-ID: <200704030449.l334nUid018723@grass.itc.it> Author: barton Date: 2007-04-03 06:49:19 +0200 (Tue, 03 Apr 2007) New Revision: 409 Modified: trunk/grassaddons/gui/Init.sh trunk/grassaddons/gui/wxgrass Log: Implementing Glynn Clement's suggestions. Change to start gui non-modally from init.sh. Script wxgrass must be called with & run non-modally Modified: trunk/grassaddons/gui/Init.sh =================================================================== --- trunk/grassaddons/gui/Init.sh 2007-04-03 02:53:39 UTC (rev 408) +++ trunk/grassaddons/gui/Init.sh 2007-04-03 04:49:19 UTC (rev 409) @@ -758,7 +758,7 @@ wx) # comming soon, see ^ - "$GISBASE/scripts/wxgrass" + "$GISBASE/scripts/wxgrass &" ;; # Ignore others @@ -792,7 +792,7 @@ if [ "$GRASS_GUI" = "text" ] ; then echo "Start the graphical user interface with: gis.m" else - echo "If required, restart the graphical user interface with: wxgrass" + echo "If required, restart the graphical user interface with: wxgrass &" fi echo "When ready to quit enter: exit" Modified: trunk/grassaddons/gui/wxgrass =================================================================== --- trunk/grassaddons/gui/wxgrass 2007-04-03 02:53:39 UTC (rev 408) +++ trunk/grassaddons/gui/wxgrass 2007-04-03 04:49:19 UTC (rev 409) @@ -8,9 +8,9 @@ SYSTEM=`uname -s` if [ "$SYSTEM" = "Darwin" ] ; then - pythonw "$GISBASE/etc/wx/wxgui.py" -name wxgui_py & + pythonw "$GISBASE/etc/wx/wxgui.py" -name wxgui_py else - python "$GISBASE/etc/wx/wxgui.py" -name wxgui_py & + python "$GISBASE/etc/wx/wxgui.py" -name wxgui_py fi exit 0 From barton at grass.itc.it Tue Apr 3 09:07:57 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Tue Apr 3 09:07:58 2007 Subject: [grass-addons] r410 - trunk/grassaddons/gui/gui_modules Message-ID: <200704030707.l3377vj1020174@grass.itc.it> Author: barton Date: 2007-04-03 09:07:44 +0200 (Tue, 03 Apr 2007) New Revision: 410 Modified: trunk/grassaddons/gui/gui_modules/render.py trunk/grassaddons/gui/gui_modules/wxgui_utils.py Log: Fixed most overlay and opacity bugs. Modified: trunk/grassaddons/gui/gui_modules/render.py =================================================================== --- trunk/grassaddons/gui/gui_modules/render.py 2007-04-03 04:49:19 UTC (rev 409) +++ trunk/grassaddons/gui/gui_modules/render.py 2007-04-03 07:07:44 UTC (rev 410) @@ -813,16 +813,17 @@ def addLayer(self, item, command, mapset=None, l_active=True, l_hidden=False, l_opacity=1, l_render=False): """ - Adds generic layer to list of layers + Adds generic display command layer to list of layers Layer Attributes: - command - display command - mapset - mapset name, default: current + command - display command + mapset - mapset name for backward compatibility. + - included in mapname in command - l_active - see MapLayer class - l_hidden - l_opacity - l_render - render an image + l_active - checked/not checked for display in layer tree + l_hidden - not used here + l_opacity - range from 0-1 + l_render - render an image if False Returns: Added layer on success or None @@ -852,12 +853,11 @@ def delLayer(self, item): """ - Removes layer from list of layers, defined by layer ID + Removes layer from list of layers, defined by layer + tree item ID Parameters: - name - map name - mapset - mapset name, default: current - id - index of the layer in layer list + item - wxPython ID for layer tree item Returns: Removed layer on success or None @@ -879,19 +879,35 @@ return None def reorderLayers(self, item_list): - - # make a new reordered list + """ + Make a new reordered list to match reordered + layer tree - for drag and drop + """ temp = [] for item in item_list: - temp.append(self.lookup[item]) + layer = self.lookup[item] + temp.append(layer) # replace original layers list with reordered one self.layers = temp + return self.layers[-1] + def updateLookup(self, olditem, newitem): + """ + Changes layer tree item associatd with rendering layer + in the lookup dictionary. Used with layer drag and drop. + """ + layer = self.lookup[olditem] + self.lookup[newitem] = layer + # old lookup item will be deleted when layer is deleted + def changeLayer(self, item, command, mapset=None, l_active=True, l_hidden=False, l_opacity=1, l_render=False): + """ + Change the command and other other options for a layer + """ if not mapset: mapset = self.env["MAPSET"] @@ -919,6 +935,9 @@ return self.layers[-1] def changeOpacity(self, item, l_opacity): + """ + Changes opacity value for rendering. + """ # l_opacity must be <0;1> if l_opacity < 0: l_opacity = 0 elif l_opacity > 1: l_opacity = 1 @@ -926,6 +945,9 @@ layer.opacity = l_opacity def changeActive(self, item, activ): + """ + Change the active state of a layer + """ layer = self.lookup[item] layer.active = activ Modified: trunk/grassaddons/gui/gui_modules/wxgui_utils.py =================================================================== --- trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-03 04:49:19 UTC (rev 409) +++ trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-03 07:07:44 UTC (rev 410) @@ -164,7 +164,7 @@ '', ct_type=1, wnd=self.ctrl ) elif (self.layer_selected and self.layer_selected != self.GetRootItem() and \ self.layertype[self.layer_selected] == 'group'): - layer = self.InsertItem(self.layer_selected, self.layer_selected, + layer = self.PrependItem(self.layer_selected, '', ct_type=1, wnd=self.ctrl ) self.Expand(self.layer_selected) else: @@ -314,7 +314,6 @@ event.Skip() def onOpacity(self, event): - print 'opacity event:', event.GetString() if 'Spin' in str(event.GetEventObject()): layer = self.layerctrl[event.GetEventObject()] else: @@ -323,8 +322,7 @@ if self.drag == False: # change opacity parameter for item in layers list in render.Map - self.changeOpacity(layer, opacity) - event.Skip() + self.Map.changeOpacity(layer, opacity) def onChangeSel(self, event): layer = event.GetItem() @@ -423,24 +421,19 @@ self.layerctrl[newctrl] = new newctrl.SetValue(self.saveitem['windval']) + # update lookup dictionary in render.Map + if self.saveitem['type'] != 'group': + self.Map.updateLookup(old, new) + # delete layer at original position self.Delete(old) # entry in render.Map layers list automatically deleted by onDeleteLayer handler - # Add new layer to layers list in render.Map - if self.saveitem['type'] == 'command': - self.Map.addLayer(item=new, command=self.saveitem['windval'], l_active=self.saveitem['check'], - l_hidden=False, l_opacity=1, l_render=False) + # reorder layers in render.Map to match new order after drag and drop + self.reorderLayers() - elif self.saveitem['type'] != 'group': - self.Map.addLayer(item=new, command=self.saveitem['data'][0], l_active=self.saveitem['check'], - l_hidden=False, l_opacity=self.saveitem['windval'], l_render=False) - # completed drag and drop self.drag = False - # reorder layers in render.Map to match new order after drag and drop - self.reorderLayers() - def getOptData(self, dcmd, layer, params): for item in dcmd.split(' '): if 'map=' in item: From barton at grass.itc.it Tue Apr 3 09:12:35 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Tue Apr 3 09:12:36 2007 Subject: [grass-addons] r411 - trunk/grassaddons/gui Message-ID: <200704030712.l337CZoI020203@grass.itc.it> Author: barton Date: 2007-04-03 09:12:25 +0200 (Tue, 03 Apr 2007) New Revision: 411 Modified: trunk/grassaddons/gui/Init.sh Log: Fixed bug in wxgrass startup. Now it starts non-modally. Modified: trunk/grassaddons/gui/Init.sh =================================================================== --- trunk/grassaddons/gui/Init.sh 2007-04-03 07:07:44 UTC (rev 410) +++ trunk/grassaddons/gui/Init.sh 2007-04-03 07:12:25 UTC (rev 411) @@ -758,7 +758,7 @@ wx) # comming soon, see ^ - "$GISBASE/scripts/wxgrass &" + "$GISBASE/scripts/wxgrass" & ;; # Ignore others From cepicky at grass.itc.it Tue Apr 3 10:25:12 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Tue Apr 3 10:25:13 2007 Subject: [grass-addons] r412 - trunk/grassaddons/gui/gui_modules Message-ID: <200704030825.l338PCt6020400@grass.itc.it> Author: cepicky Date: 2007-04-03 10:25:12 +0200 (Tue, 03 Apr 2007) New Revision: 412 Modified: trunk/grassaddons/gui/gui_modules/dbm.py Log: status information works now Modified: trunk/grassaddons/gui/gui_modules/dbm.py =================================================================== --- trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-03 07:12:25 UTC (rev 411) +++ trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-03 08:25:12 UTC (rev 412) @@ -20,12 +20,11 @@ r"""\brief Needed by the wxdemos. The log output is redirected to the status bar of the containing frame. """ + def __init__(self,parent): + self.parent = parent - def WriteText(self,text_string): - self.write(text_string) - def write(self,text_string): - wx.GetApp().GetTopWindow().SetStatusText(text_string) + self.parent.SetStatusText(text_string.strip()) #---------------------------------------------------------------------- # The panel you want to test (TestVirtualList) @@ -89,13 +88,13 @@ def OnItemSelected(self, event): self.currentItem = event.m_itemIndex - self.log.WriteText('OnItemSelected: "%s", "%s"\n' % + self.log.write('OnItemSelected: "%s", "%s"\n' % (self.currentItem, self.GetItemText(self.currentItem))) def OnItemActivated(self, event): self.currentItem = event.m_itemIndex - self.log.WriteText("OnItemActivated: %s\nTopItem: %s\n" % + self.log.write("OnItemActivated: %s\nTopItem: %s\n" % (self.GetItemText(self.currentItem), self.GetTopItem())) def getColumnText(self, index, col): @@ -103,7 +102,7 @@ return item.GetText() def OnItemDeselected(self, evt): - self.log.WriteText("OnItemDeselected: %s" % evt.m_itemIndex) + self.log.write("OnItemDeselected: %s" % evt.m_itemIndex) #--------------------------------------------------- @@ -173,7 +172,7 @@ self.CreateStatusBar(1) - log=Log() + log=Log(self) self.win = TestVirtualList(self, log,tablename=table) self.Show() From neteler at grass.itc.it Tue Apr 3 13:35:46 2007 From: neteler at grass.itc.it (neteler@grass.itc.it) Date: Tue Apr 3 13:35:47 2007 Subject: [grass-addons] r413 - in trunk/grassaddons: . i.landsat.dehaze Message-ID: <200704031135.l33BZkUj022602@grass.itc.it> Author: neteler Date: 2007-04-03 13:35:46 +0200 (Tue, 03 Apr 2007) New Revision: 413 Added: trunk/grassaddons/i.landsat.dehaze/ trunk/grassaddons/i.landsat.dehaze/Makefile trunk/grassaddons/i.landsat.dehaze/description.html trunk/grassaddons/i.landsat.dehaze/i.landsat.dehaze Log: i.landsat.dehaze ported to GRASS 6 (former i.tm.dehaze) Added: trunk/grassaddons/i.landsat.dehaze/Makefile =================================================================== --- trunk/grassaddons/i.landsat.dehaze/Makefile (rev 0) +++ trunk/grassaddons/i.landsat.dehaze/Makefile 2007-04-03 11:35:46 UTC (rev 413) @@ -0,0 +1,7 @@ +MODULE_TOPDIR = ../.. + +PGM = i.landsat.dehaze + +include $(MODULE_TOPDIR)/include/Make/Script.make + +default: script Added: trunk/grassaddons/i.landsat.dehaze/description.html =================================================================== --- trunk/grassaddons/i.landsat.dehaze/description.html (rev 0) +++ trunk/grassaddons/i.landsat.dehaze/description.html 2007-04-03 11:35:46 UTC (rev 413) @@ -0,0 +1,36 @@ +

DESCRIPTION

+ +i.landsat.dehaze applies a bandwise haze correction using tasscap4 (haze) +and linear regression. + +

NOTES

+ +The regression based technique which determines a 'best fit' line for +multispectral plots of pixels within homogenous cover types. The slope of +the plot is proportional to the ratio of the reflective material (Crippen +1987). + +

EXAMPLE

+ +
+# preparation of Tasseled Cap "haze" map
+i.tasscap -7 band1=tm.10 band2=tm.20 band3=tm.30 band4=tm.40 band5=tm.50 band7=tm.70 out=tasscap
+
+# De-hazing
+i.landsat.dehaze band1=tm.10 band2=tm.20 band3=tm.30 band4=tm.40 band5=tm.50 band7=tm.70 tasscap4=tasscap.4
+
+ + +

SEE ALSO

+ +d.rgb, +g.region, +i.tasscap, +r.colors, +r.composite + +

AUTHOR

+ +Markus Neteler + +

Last changed: $Date: 2006/09/07 09:24:10 $ Added: trunk/grassaddons/i.landsat.dehaze/i.landsat.dehaze =================================================================== --- trunk/grassaddons/i.landsat.dehaze/i.landsat.dehaze (rev 0) +++ trunk/grassaddons/i.landsat.dehaze/i.landsat.dehaze 2007-04-03 11:35:46 UTC (rev 413) @@ -0,0 +1,198 @@ +#!/bin/sh + +# $Id: i.tm.dehaze,v 1.5 2002/10/30 08:51:49 markus Exp $ +# copyright Markus Neteler +# License: GNU GPL +# +# Methodology: +# Bandwise correction using tasscap4 (haze) and linear regression. +# (Crippen 1987 approach) +# +# The regression based technique which determines a 'best fit' line for +# multispectral plots of pixels within homogenous cover types. The slope of +# the plot is proportional to the ratio of the reflective material (Crippen +# 1987). +# +# http://www.forestry.umt.edu/academics/courses/FOR503/rs1.htm: +# The correction technique is based on the fact that Thematic Mapper (TM) +# band 7 is essentially free from atmospheric effects. Upon examining an +# area in the image that is in deep shadow or a body of homogeneous deep +# nonturbid water, the resulting reflectance value in band 7 is either 0 or +# 1. A histogram of the reflectivity values in band 7 for this area starts +# from 0 or 1. On the contrary, a histogram of the reflectivity values in +# bands 1, 2 and 3 for the same area starts from much higher values as a +# result of haze. This offset, characteristic for each one of the three +# bands is subtracted from the initial reflectance values and the result is +# a haze corrected image (Sabins 1987). +# +# The formular of linear regression is (r.linear.regression): +# y=a[0]x[0] + a[1] + + +#%Module +#% description: De-hazing of a LANDSAT scene +#% keywords: raster, imagery, dehaze +#%End +#%Option +#% key: band1 +#% type: string +#% required: yes +#% multiple: no +#% description: LANDSAT TM1 channel +#% gisprompt: old,cell,raster +#%End +#%Option +#% key: band2 +#% type: string +#% required: yes +#% multiple: no +#% description: LANDSAT TM2 channel +#% gisprompt: old,cell,raster +#%End +#%Option +#% key: band3 +#% type: string +#% required: yes +#% multiple: no +#% description: LANDSAT TM3 channel +#% gisprompt: old,cell,raster +#%End +#%Option +#% key: band4 +#% type: string +#% required: yes +#% multiple: no +#% description: LANDSAT TM4 channel +#% gisprompt: old,cell,raster +#%End +#%Option +#% key: band5 +#% type: string +#% required: yes +#% multiple: no +#% description: LANDSAT TM5 channel +#% gisprompt: old,cell,raster +#%End +#%Option +#% key: band7 +#% type: string +#% required: yes +#% multiple: no +#% description: LANDSAT TM7 channel +#% gisprompt: old,cell,raster +#%End +#%Option +#% key: tasscap4 +#% type: string +#% required: yes +#% multiple: no +#% description: Tasseled Cap 4 haze map +#% gisprompt: old,cell,raster +#%End + +if [ -z "$GISBASE" ] ; then + echo "You must be in GRASS GIS to run this program." 1>&2 + exit 1 +fi + +if [ "$1" != "@ARGS_PARSED@" ] ; then + exec g.parser "$0" "$@" +fi + +# setting environment, so that awk works properly in all languages +unset LC_ALL +LC_NUMERIC=C +export LC_NUMERIC + +PROG=`basename $0` + +#define the names: +tm1=$GIS_OPT_band1 +tm2=$GIS_OPT_band2 +tm3=$GIS_OPT_band3 +tm4=$GIS_OPT_band4 +tm5=$GIS_OPT_band5 +tm7=$GIS_OPT_band7 +tasscap=$GIS_OPT_tasscap4 + +#test for file: +eval `g.findfile el=cell file=$GIS_OPT_band1` +if [ ! "$file" ] ; then + echo "Raster map '$GIS_OPT_band1' not found in mapset search path" + exit 1 +fi +eval `g.findfile el=cell file=$GIS_OPT_tasscap4` +if [ ! "$file" ] ; then + echo "Raster map '$GIS_OPT_tasscap4' not found in mapset search path" + echo "Did you run i.tasscap?" + exit 1 +fi + +#get the stats: +getstats() +{ + r.stats -1 $tasscap,$1 > $2 + r.linear.regression in=$2 out=$3 + if [ $? -eq 1 ] ; then + echo "An error occurred. Stop." + exit + fi +# result="`tail $3 | grep a\[1\] | cut -d' ' -f2,3 | tr -s '=' ' '`" + result="`tail $3 | grep "a\[1\]" | tr -s '=' ' '`" +} + + +getstats $tm1 $tasscap.plot1 $tasscap.regress1 +slope_tm1=`echo $result | cut -d' ' -f2` +intercept_tm1=`echo $result | cut -d' ' -f4` +# clean up the tmp stuff: +rm -f $tasscap.plot1 $tasscap.regress1 +echo "Result for channel $tm1: sl:$slope_tm1 i:$intercept_tm1" +echo "" + +getstats $tm2 $tasscap.plot2 $tasscap.regress2 +slope_tm2=`echo $result | cut -d' ' -f2` +intercept_tm2=`echo $result | cut -d' ' -f4` +# clean up the tmp stuff: +rm -f $tasscap.plot2 $tasscap.regress2 +echo "Result for channel $tm2: $slope_tm2 i:$intercept_tm2" +echo "" + +getstats $tm3 $tasscap.plot3 $tasscap.regress3 +slope_tm3=`echo $result | cut -d' ' -f2` +intercept_tm3=`echo $result | cut -d' ' -f4` +# clean up the tmp stuff: +rm -f $tasscap.plot3 $tasscap.regress3 +echo "Result for channel $tm3: $slope_tm3 i:$intercept_tm3" +echo "" + +getstats $tm4 $tasscap.plot4 $tasscap.regress4 +slope_tm4=`echo $result | cut -d' ' -f2` +intercept_tm4=`echo $result | cut -d' ' -f4` +# clean up the tmp stuff: +rm -f $tasscap.plot4 $tasscap.regress4 +echo "Result for channel $tm4: $slope_tm4 i:$intercept_tm4" +echo "" + +getstats $tm5 $tasscap.plot5 $tasscap.regress5 +slope_tm5=`echo $result | cut -d' ' -f2` +intercept_tm5=`echo $result | cut -d' ' -f4` +# clean up the tmp stuff: +rm -f $tasscap.plot5 $tasscap.regress5 +echo "Result for channel $tm5: $slope_tm5 i:$intercept_tm5" +echo "" + +# do the correction: +echo "Running the dehaze formula: tm.dehaze = tm - [ (tass4 - tm_i) * tm_s]" +echo " with internally calculated i:intercept, s: slope" + +r.mapcalc $tm1.dehaze="$tm1 - ( ($tasscap - $intercept_tm1) * $slope_tm1)" +r.mapcalc $tm2.dehaze="$tm2 - ( ($tasscap - $intercept_tm2) * $slope_tm2)" +r.mapcalc $tm3.dehaze="$tm3 - ( ($tasscap - $intercept_tm3) * $slope_tm3)" +r.mapcalc $tm4.dehaze="$tm4 - ( ($tasscap - $intercept_tm4) * $slope_tm4)" +r.mapcalc $tm5.dehaze="$tm5 - ( ($tasscap - $intercept_tm5) * $slope_tm5)" +echo "" +echo "The new dehazed bands are: " +echo "$tm1.dehaze, $tm2.dehaze, $tm3.dehaze, $tm4.dehaze, $tm5.dehaze" +echo "" +echo "Consider to run r.colors or i.grey.scale to set a grey color table" Property changes on: trunk/grassaddons/i.landsat.dehaze/i.landsat.dehaze ___________________________________________________________________ Name: svn:executable + * From cepicky at grass.itc.it Tue Apr 3 16:04:40 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Tue Apr 3 16:04:41 2007 Subject: [grass-addons] r414 - trunk/grassaddons/gui/gui_modules Message-ID: <200704031404.l33E4eCZ024662@grass.itc.it> Author: cepicky Date: 2007-04-03 16:04:39 +0200 (Tue, 03 Apr 2007) New Revision: 414 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py Log: basic layer queries Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-03 11:35:46 UTC (rev 413) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-03 14:04:39 UTC (rev 414) @@ -564,7 +564,7 @@ # left mouse button released and not just a pointer elif event.LeftUp(): - if self.mouse['box'] != "point": + if self.mouse['box'] != "point" and self.mouse['box'] != "query": # end point of zoom box or drag self.mouse['end'] = event.GetPositionTuple()[:] @@ -582,6 +582,19 @@ # redraw map self.render=True self.UpdateMap() + + # quering + elif self.mouse["box"] == "query": + east,north = self.Pixel2Cell(self.mouse['end'][0],self.mouse['end'][1]) + if self.parent.gismanager: + layer = self.parent.gismanager.maptree.GetSelection() + type = self.parent.gismanager.maptree.layertype[layer] + map,mapset = layer.GetText().split("@") + self.parent.QueryMap(map,mapset,type,east,north) + else: + print "Quering without gis manager not implemented yet" + + elif self.dragid: self.Refresh() self.Update() @@ -816,6 +829,9 @@ wx.Frame.__init__(self, parent, id, title, pos, size, style) + # most of the thime, this will be the gis manager + self.gismanager = parent + # # Set the size # @@ -1101,6 +1117,35 @@ """ return Map + def OnQuery(self, event): + """ + Query currrent or last map + """ + print "Quering" + self.MapWindow.mouse['box'] = "query" + self.MapWindow.zoomtype = 0 + #event.Skip() + + # change the cursor + self.MapWindow.SetCursor (self.cursors["cross"]) + + def QueryMap(self,name,mapset,type,x,y): + """ + Run *.what command in gis manager output window + """ + if type == "raster": + cmd = "r.what input=%s east_north=%f,%f" %\ + (name+"@"+mapset, float(x), float(y)) + + elif type == "vector": + cmd = "v.what map=%s east_north=%f,%f"%\ + (name+"@"+mapset, float(x), float(y)) + + if self.gismanager: + self.gismanager.goutput.runCmd(cmd) + else: + os.system(cmd) + # toolBar button handlers def onDecoration(self, event): """ From cepicky at grass.itc.it Tue Apr 3 16:05:07 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Tue Apr 3 16:05:08 2007 Subject: [grass-addons] r415 - trunk/grassaddons/gui/gui_modules Message-ID: <200704031405.l33E577Z024693@grass.itc.it> Author: cepicky Date: 2007-04-03 16:05:07 +0200 (Tue, 03 Apr 2007) New Revision: 415 Modified: trunk/grassaddons/gui/gui_modules/toolbars.py Log: basic layer queries Modified: trunk/grassaddons/gui/gui_modules/toolbars.py =================================================================== --- trunk/grassaddons/gui/gui_modules/toolbars.py 2007-04-03 14:04:39 UTC (rev 414) +++ trunk/grassaddons/gui/gui_modules/toolbars.py 2007-04-03 14:05:07 UTC (rev 415) @@ -64,6 +64,11 @@ wx.BITMAP_TYPE_ANY), bmpDisabled=wx.NullBitmap, kind=wx.ITEM_RADIO, shortHelp="Pan", longHelp="Drag with mouse to pan") + self.query = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="query", + bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"gui-query.gif"), + wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, kind=wx.ITEM_RADIO, + shortHelp="Query", longHelp="Query selected map") self.toolbar.AddSeparator() @@ -73,7 +78,6 @@ bmpDisabled=wx.NullBitmap, shortHelp="Decoration", longHelp="Add graphic overlays to map") - self.toolbar.AddSeparator() # @@ -104,6 +108,7 @@ self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnZoomOut, self.zoomout) self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnPan, self.pan) self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.onDecoration, self.dec) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnQuery, self.query) self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnErase, self.erase) self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.SaveToFile, self.savefile) self.mapdisplay.Bind(wx.EVT_COMBOBOX, self.OnSelect, self.combo) From cepicky at grass.itc.it Tue Apr 3 16:09:49 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Tue Apr 3 16:09:51 2007 Subject: [grass-addons] r416 - trunk/grassaddons/gui/gui_modules Message-ID: <200704031409.l33E9nxc024737@grass.itc.it> Author: cepicky Date: 2007-04-03 16:09:49 +0200 (Tue, 03 Apr 2007) New Revision: 416 Modified: trunk/grassaddons/gui/gui_modules/dbm.py Log: sorting works better Modified: trunk/grassaddons/gui/gui_modules/dbm.py =================================================================== --- trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-03 14:05:07 UTC (rev 415) +++ trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-03 14:09:49 UTC (rev 416) @@ -10,6 +10,18 @@ dbm.py table_name """ +############################################################################ +# +# MODULE: dbm.py +# AUTHOR(S): Jachym Cepicky +# PURPOSE: GRASS attribute table manager +# COPYRIGHT: (C) 2007 by the GRASS Development Team +# +# This program is free software under the GNU General Public +# License (>=v2). Read the file COPYING that comes with GRASS +# for details. +# +############################################################################# import wx import wx.lib.mixins.listctrl as listmix @@ -33,8 +45,12 @@ class TestVirtualList(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.ColumnSorterMixin): def __init__(self, parent,log,tablename): wx.ListCtrl.__init__( self, parent, -1, style=wx.LC_REPORT|wx.LC_VIRTUAL|wx.LC_HRULES|wx.LC_VRULES) + self.log=log self.tablename = tablename + self.columns = [] + self.columnNumber = 0 + self.parent = parent #adding some attributes (colourful background for each item rows) self.attr1 = wx.ListItemAttr() @@ -46,13 +62,26 @@ i = 0 # FIXME: subprocess.Popen should be used # FIXME: Maximal number of columns, when the GUI is still usable - for column in os.popen("db.columns table=%s" % - (self.tablename)).readlines(): + for line in os.popen("db.describe -c table=%s" % + (self.tablename)).readlines()[1:]: - column = column.strip() + x,column,type = line.strip().split(":") + # FIXME: here will be more types + if type.lower().find("integer") > -1: + self.columns.append({"name":column,"type":int}) + elif type.lower().find("double") > -1: + self.columns.append({"name":column,"type":float}) + elif type.lower().find("float") > -1: + self.columns.append({"name":column,"type":float}) + else: + self.columns.append({"name":column,"type":str}) + self.InsertColumn(i, column) self.SetColumnWidth(i, 50) i += 1 + if i >= 256: + self.log.write("Can display only 256 columns") + break #These two should probably be passed to init more cleanly #setting the numbers of items = number of elements in the dictionary @@ -68,6 +97,10 @@ self.itemDataMap[i].append(attribute) self.itemIndexMap.append(i) i += 1 + if i >= 32000: + self.log.write("Can display only 32000 lines") + break + self.SetItemCount(len(self.itemDataMap)) #mixins @@ -84,6 +117,7 @@ self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick) def OnColClick(self,event): + self.columnNumber = event.GetColumn() event.Skip() def OnItemSelected(self, event): @@ -91,6 +125,7 @@ self.log.write('OnItemSelected: "%s", "%s"\n' % (self.currentItem, self.GetItemText(self.currentItem))) + print self.parent.gismanager def OnItemActivated(self, event): self.currentItem = event.m_itemIndex @@ -135,7 +170,12 @@ def SortItems(self,sorter=cmp): items = list(self.itemDataMap.keys()) - items.sort(sorter) + # for i in range(len(items)): + # items[i] = self.columns[self.columnNumber]["type"](items[i]) + items.sort(self.Sorter) + #items.sort(sorter) + # for i in range(len(items)): + # items[i] = str(items[i]) self.itemIndexMap = items # redraw the list @@ -145,6 +185,33 @@ def GetListCtrl(self): return self + # stolen from python2.4/site-packages/wx-2.8-gtk2-unicode/wx/lib/mixins/listctrl.py + def Sorter(self, key1,key2): + col = self._col + ascending = self._colSortFlag[col] + # convert, because the it is allways string + item1 = self.columns[col]["type"](self.itemDataMap[key1][col]) + item2 = self.columns[col]["type"](self.itemDataMap[key2][col]) + + #--- Internationalization of string sorting with locale module + if type(item1) == type('') or type(item2) == type(''): + cmpVal = locale.strcoll(str(item1), str(item2)) + else: + cmpVal = cmp(item1, item2) + #--- + + # If the items are equal then pick something else to make the sort v ->alue unique + if cmpVal == 0: + cmpVal = apply(cmp, self.GetSecondarySortValues(col, key1, key2)) + + if ascending: + return cmpVal + else: + return -cmpVal + + #return cmp(self.columns[self.columnNumber]["type"](a), + # self.columns[self.columnNumber]["type"](b)) + # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py #def GetSortImages(self): # return (self.sm_dn, self.sm_up) @@ -173,6 +240,9 @@ self.CreateStatusBar(1) log=Log(self) + + # probably + self.gismanager = parent self.win = TestVirtualList(self, log,tablename=table) self.Show() From neteler at grass.itc.it Tue Apr 3 16:24:38 2007 From: neteler at grass.itc.it (neteler@grass.itc.it) Date: Tue Apr 3 16:24:40 2007 Subject: [grass-addons] r417 - in trunk/grassaddons: . r.out.netcdf Message-ID: <200704031424.l33EOcXj024840@grass.itc.it> Author: neteler Date: 2007-04-03 16:24:38 +0200 (Tue, 03 Apr 2007) New Revision: 417 Added: trunk/grassaddons/r.out.netcdf/ trunk/grassaddons/r.out.netcdf/COPYING trunk/grassaddons/r.out.netcdf/LICENSE trunk/grassaddons/r.out.netcdf/Makefile trunk/grassaddons/r.out.netcdf/NEWS trunk/grassaddons/r.out.netcdf/TODO trunk/grassaddons/r.out.netcdf/description.html trunk/grassaddons/r.out.netcdf/main.c Log: r.out.netcdf added Added: trunk/grassaddons/r.out.netcdf/COPYING =================================================================== --- trunk/grassaddons/r.out.netcdf/COPYING (rev 0) +++ trunk/grassaddons/r.out.netcdf/COPYING 2007-04-03 14:24:38 UTC (rev 417) @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. Added: trunk/grassaddons/r.out.netcdf/LICENSE =================================================================== --- trunk/grassaddons/r.out.netcdf/LICENSE (rev 0) +++ trunk/grassaddons/r.out.netcdf/LICENSE 2007-04-03 14:24:38 UTC (rev 417) @@ -0,0 +1,5 @@ +r.out.netcdf is made to be part of GRASS GIS and is relased under the +terms of General Public license ver.2. that comes together with this +package (see GPL.txt) + +copyright - Alessandro Frigeri, 2003 Added: trunk/grassaddons/r.out.netcdf/Makefile =================================================================== --- trunk/grassaddons/r.out.netcdf/Makefile (rev 0) +++ trunk/grassaddons/r.out.netcdf/Makefile 2007-04-03 14:24:38 UTC (rev 417) @@ -0,0 +1,10 @@ +MODULE_TOPDIR = ../.. + +PGM = r.out.netcdf + +LIBES = $(GISLIB) -lnetcdf +DEPENDENCIES = $(GISDEP) + +include $(MODULE_TOPDIR)/include/Make/Module.make + +default: cmd Added: trunk/grassaddons/r.out.netcdf/NEWS =================================================================== --- trunk/grassaddons/r.out.netcdf/NEWS (rev 0) +++ trunk/grassaddons/r.out.netcdf/NEWS 2007-04-03 14:24:38 UTC (rev 417) @@ -0,0 +1,6 @@ +18 Feb 2007 - M Neteler + updated to GRASS 6.3 +11 Feb 2003 - A Frigeri + r.out.netcdf (0.1) is out for testing! +17 Mar 2003 - A Frigeri + added support for floating point and double precision raster maps Added: trunk/grassaddons/r.out.netcdf/TODO =================================================================== --- trunk/grassaddons/r.out.netcdf/TODO (rev 0) +++ trunk/grassaddons/r.out.netcdf/TODO 2007-04-03 14:24:38 UTC (rev 417) @@ -0,0 +1 @@ +-Align netcdf and grass datatypes Added: trunk/grassaddons/r.out.netcdf/description.html =================================================================== --- trunk/grassaddons/r.out.netcdf/description.html (rev 0) +++ trunk/grassaddons/r.out.netcdf/description.html 2007-04-03 14:24:38 UTC (rev 417) @@ -0,0 +1,19 @@ +

DESCRIPTION

+ +The r.out.netcdf module exports a GRASS raster map to a netcdf file. +Dimensions, Attributes and variables of netcdf file follow the scheme used +in Generic Mapping Tools (GMT) grid (.grd) files (-g flag) or are suitable +to be imported into IBM Data Explorer (-d flag). + +

AUTHOR

+ +Alessandro Frigeri <afrigeri unipg it> + +

SEE ALSO

+ +r.out.arc, +r.out.ascii, +r.out.bin, +r.out.tiff + +

Last changed: $Date: 2004/11/17 17:49:04 $ Added: trunk/grassaddons/r.out.netcdf/main.c =================================================================== --- trunk/grassaddons/r.out.netcdf/main.c (rev 0) +++ trunk/grassaddons/r.out.netcdf/main.c 2007-04-03 14:24:38 UTC (rev 417) @@ -0,0 +1,385 @@ +/**************************************************************************** + * + * MODULE: r.out.netcdf + * AUTHOR(S): Alessandro Frigeri - afrigeri@unipg.it + * + * PURPOSE: Exports a raster map in a netCDF file. + * Exported map can be imported easly into IBM data Explorer + * or Generic Mapping Tools (GMT) + * + * COPYRIGHT: (C) 2003 Alessandro Frigeri for the GRASS Development Team + * + * This program is free software under the GNU General Public + * License (>=v2). Read the file COPYING that comes with GRASS + * for details. + * + * LAST UPDATE: 10th Feb 2003 + * + * HISTORY: 11th Feb 2003 - r.out.necdf is out + * + * 16th Mar 2003 - support for FCELL and DCELL added + *****************************************************************************/ + +#include +#include +#include + +#include + +#include +#include + +extern CELL f_c(CELL); +extern FCELL f_f(FCELL); +extern DCELL f_d(DCELL); + +/* Stuff to make calculation on the raster values */ + +CELL c_calc(CELL x) +{ + return x; +} + +FCELL f_calc(FCELL x) +{ + return x; +} + +DCELL d_calc(DCELL x) +{ + return x; +} + +int +main(int argc, char *argv[]) +{ + struct Cell_head cellhd; + struct Cell_head region; + char *name, *result, *mapset; + void *inrast; + unsigned char *outrast; + int nrows, ncols; + int row,col; + int infd; + int dx,gmt; + RASTER_MAP_TYPE data_type; + struct GModule *module; + struct Option *input, *output; + struct Flag *flag1, *flag2; + + /* netcdf stuff */ + int ncols_dim,nrows_dim,naxes_dim,height_id,ndeltas_dim; + int errnc,ncID,zid,xrangeid,yrangeid,zrangeid,spacingid,dimensionid; + int fieldid,fielddimid; + int locations_id; + char title1[NC_MAX_NAME]="GRASS GIS raster map",history[NC_MAX_NAME]; + static char title[] = "netCDF output from GRASS GIS"; + static char remark[] = "netCDF output from GRASS GIS"; + static char degrees[] = "degrees"; + static char meter[] = "Meter"; + static float scale_factor[]={1}; + static int add_offset[]={0}; + static int node_offset[]={1}; + int sideid,xysizeid; + static size_t a[] = {0}; + static size_t b[] = {1}; + int xysize; + int vali; + float valf; + double vald; + size_t zstart[1]; + size_t zcount[1]; + double fmin,fmax; + double domin,domax; + int dmin,dmax; + struct FPRange fprange; + struct Range range; + float longitude,latitude; + + + int locations_dims[2]; + + int height_dims[2]; + + static size_t index_height[]={0,0}; + static size_t index_locations[]={0,0}; + + double ns; + + G_gisinit(argv[0]); + + module = G_define_module(); + module->description = + _("GRASS module for exporting netCDF data"); + + /* Define options */ + + input = G_define_standard_option(G_OPT_R_INPUT); + + output = G_define_standard_option(G_OPT_R_OUTPUT); + output->gisprompt = "new_file,file,output"; + output->description= "File name for new netCDF file"; + + /* Define the different flags */ + flag1 = G_define_flag() ; + flag1->key = 'd' ; + flag1->description = _("OpenDX output") ; + + flag2 = G_define_flag() ; + flag2->key = 'g' ; + flag2->description = _("GMT output") ; + + if (G_parser(argc, argv)) + exit (EXIT_FAILURE); + + name = input->answer; + result = output->answer; + dx = flag1->answer; + gmt = flag2->answer; + + /* find map in mapset */ + mapset = G_find_cell2 (name, ""); + if (mapset == NULL) + G_fatal_error ("cell file [%s] not found", name); + + if (G_legal_filename (result) < 0) + G_fatal_error ("[%s] is an illegal name", result); + + /* determine the inputmap type (CELL/FCELL/DCELL) */ + data_type = G_raster_map_type(name, mapset); + + if ( (infd = G_open_cell_old (name, mapset)) < 0) + G_fatal_error ("Cannot open cell file [%s]", name); + + if (G_get_cellhd (name, mapset, &cellhd) < 0) + G_fatal_error ("Cannot read file header of [%s]", name); + + /* Allocate input buffer */ + inrast = G_allocate_raster_buf(data_type); + + /* Allocate output buffer, use input map data_type */ + + nrows = G_window_rows(); + ncols = G_window_cols(); + outrast = G_allocate_raster_buf(data_type); + + /* Get window boundaries */ + G_get_window(®ion); + + /* Create the new netCDF file */ + + errnc = nc_create(result, NC_WRITE, &ncID); + if (errnc == NC_EEXIST) + printf("Couldn't create NC file %s, NC file with that name already exists\n",result); + + + + /* Dimensions: xysize, side */ + ncols=region.cols; + nrows=region.rows; + + xysize=ncols*nrows; + + if(gmt){ + nc_def_dim(ncID, "xysize", xysize, &xysizeid); + nc_def_dim(ncID, "side", 2, &sideid); + nc_def_var(ncID, "x_range", NC_DOUBLE,1, &sideid,&xrangeid); + nc_put_att_text(ncID, xrangeid, "units",strlen(degrees), degrees); + nc_def_var(ncID, "y_range", NC_DOUBLE,1, &sideid,&yrangeid); + nc_put_att_text(ncID, yrangeid, "units",strlen(degrees), degrees); + nc_def_var(ncID, "z_range", NC_DOUBLE,1, &sideid,&zrangeid); + nc_put_att_text(ncID, zrangeid, "units",strlen(meter), meter); + nc_def_var(ncID, "spacing", NC_DOUBLE,1, &sideid,&spacingid); + nc_def_var(ncID, "dimension", NC_INT,1, &sideid,&dimensionid); + + switch (data_type) + { + case CELL_TYPE: + nc_def_var(ncID, "z", NC_INT,1, &xysizeid,&zid); + break; + case FCELL_TYPE: + nc_def_var(ncID, "z", NC_FLOAT,1, &xysizeid,&zid); + break; + case DCELL_TYPE: + nc_def_var(ncID, "z", NC_DOUBLE,1, &xysizeid,&zid); + break; + } + + scale_factor[0]=1; + + nc_put_att_text(ncID, NC_GLOBAL, "title",strlen(title), title); + nc_put_att_text(ncID, NC_GLOBAL, "source",strlen(title), title); + nc_put_att_text(ncID, zid, "long_name",strlen(title), title); + nc_put_att_float(ncID, zid, "scale_factor",NC_FLOAT,1, scale_factor); + nc_put_att_int(ncID, zid, "add_offset",NC_INT,1, add_offset); + nc_put_att_int(ncID, zid, "node_offset",NC_INT,1, node_offset); + } + + if(dx){ + nc_def_dim(ncID, "lon", ncols , &ncols_dim); + nc_def_dim(ncID, "lat", nrows , &nrows_dim); + nc_def_dim(ncID, "naxes",2, &naxes_dim); + nc_def_dim(ncID, "ndeltas",2, &ndeltas_dim); + + locations_dims[0]=naxes_dim; + locations_dims[1]=ndeltas_dim; + nc_def_var(ncID, "locations",NC_FLOAT,2,locations_dims,&locations_id); + + height_dims[0]=ncols_dim; + height_dims[1]=nrows_dim; + + switch (data_type) + { + case CELL_TYPE: + nc_def_var(ncID, "height",NC_INT,2,height_dims,&height_id); + break; + case FCELL_TYPE: + nc_def_var(ncID, "height",NC_FLOAT,2,height_dims,&height_id); + break; + case DCELL_TYPE: + nc_def_var(ncID, "height",NC_DOUBLE,2,height_dims,&height_id); + break; + } + + + nc_put_att_text(ncID, height_id, "field",14,"height, scalar"); + nc_put_att_text(ncID, height_id, "positions",18,"locations, regular"); + + } + nc_enddef(ncID); + + if(gmt){ + nc_put_var1_double(ncID,xrangeid,a,®ion.west); + nc_put_var1_double(ncID,xrangeid,b,®ion.east); + nc_put_var1_double(ncID,yrangeid,a,®ion.south); + nc_put_var1_double(ncID,yrangeid,b,®ion.north); + nc_put_var1_double(ncID,spacingid,a,®ion.ew_res); + nc_put_var1_double(ncID,spacingid,b,®ion.ns_res); + nc_put_var1_int(ncID,dimensionid,a,&ncols); + nc_put_var1_int(ncID,dimensionid,b,&nrows); + } + + if(dx){ + + index_locations[0]=0; + index_locations[1]=0; + nc_put_var1_double(ncID,locations_id,index_locations,®ion.west); + index_locations[0]=0; + index_locations[1]=1; + nc_put_var1_double(ncID,locations_id,index_locations,®ion.ew_res); + + index_locations[0]=1; + index_locations[1]=0; + nc_put_var1_double(ncID,locations_id,index_locations,®ion.north); + index_locations[0]=1; + index_locations[1]=1; + ns=-region.ns_res; + nc_put_var1_double(ncID,locations_id,index_locations,&ns); + + } + + + for (row = 0; row < nrows; row++) + { + CELL c; + FCELL f; + DCELL d; + + G_percent (row, nrows, 2); + + /* read input map */ + if (G_get_raster_row (infd, inrast, row, data_type) < 0) + G_fatal_error (_("Could not read from <%s>"),name); + + + /* put the NetCDF data + CELL -> int + FCELL -> float + DCELL -> double + */ + for (col=0; col < ncols; col++) + { + longitude=region.west+col*region.ew_res; + latitude=region.south+row*region.ns_res; + index_height[0]=col; + index_height[1]=row; + + switch (data_type) + { + case CELL_TYPE: + c = ((CELL *) inrast)[col]; + c = c_calc(c); /* calculate */ + zstart[0]=col+(row*ncols); /* position */ + zcount[0]=1; /* extension */ + + vali=c; + if(gmt){ + nc_put_vara_int(ncID,zid,zstart,zcount,&vali); + } + if(dx){ + nc_put_var1_int(ncID,height_id,index_height,&vali); + } + break; + case FCELL_TYPE: + f = ((FCELL *) inrast)[col]; + f = f_calc(f); /* calculate */ + zstart[0]=col+(row*ncols); /* position */ + zcount[0]=1; /* extension */ + valf=f; + if(gmt){ + nc_put_vara_float(ncID,zid,zstart,zcount,&valf); + } + if(dx){ + nc_put_var1_float(ncID,height_id,index_height,&valf); + } + break; + case DCELL_TYPE: + d = ((DCELL *) inrast)[col]; + d = d_calc(d); /* calculate */ + zstart[0]=col+(row*ncols); /* position */ + zcount[0]=1; /* extension */ + vald=d; + if(gmt){ + nc_put_vara_double(ncID,zid,zstart,zcount,&vald); + } + if(dx){ + nc_put_var1_double(ncID,height_id,index_height,&vald); + } + break; + } + } + + } + + + if(gmt) + { + switch(data_type) + { + case CELL_TYPE: + G_read_range(name, mapset, &range); + G_get_range_min_max(&range, &dmin, &dmax); + nc_put_var1_int(ncID,zrangeid,a,&dmin); + nc_put_var1_int(ncID,zrangeid,b,&dmax); + case FCELL_TYPE: + G_read_fp_range(name, mapset, &fprange); + G_get_fp_range_min_max(&fprange, &fmin, &fmax); + nc_put_var1_double(ncID,zrangeid,a,&fmin); + nc_put_var1_double(ncID,zrangeid,b,&fmax); + case DCELL_TYPE: + G_read_fp_range(name, mapset, &fprange); + G_get_fp_range_min_max(&fprange, &domin, &domax); + nc_put_var1_double(ncID,zrangeid,a,&domin); + nc_put_var1_double(ncID,zrangeid,b,&domax); + } + + } + + /* Close the netcdf file */ + nc_close(ncID); + + G_free(inrast); + G_close_cell (infd); + + exit(EXIT_SUCCESS); +} From cepicky at grass.itc.it Tue Apr 3 17:17:49 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Tue Apr 3 17:17:51 2007 Subject: [grass-addons] r418 - in trunk/grassaddons/gui: . gui_modules Message-ID: <200704031517.l33FHnU7025840@grass.itc.it> Author: cepicky Date: 2007-04-03 17:17:49 +0200 (Tue, 03 Apr 2007) New Revision: 418 Modified: trunk/grassaddons/gui/gui_modules/dbm.py trunk/grassaddons/gui/wxgui.py Log: parent for attribute table Modified: trunk/grassaddons/gui/gui_modules/dbm.py =================================================================== --- trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-03 14:24:38 UTC (rev 417) +++ trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-03 15:17:49 UTC (rev 418) @@ -125,8 +125,16 @@ self.log.write('OnItemSelected: "%s", "%s"\n' % (self.currentItem, self.GetItemText(self.currentItem))) - print self.parent.gismanager + print self.parent.gismanager + if self.parent.gismanager: + gism = self.parent.gismanager + curr_pg = gism.gm_cb.GetCurrentPage() + disp_idx = gism.track.Track().GetDisp_idx(curr_pg) + + print self.parent.gismanager.mapdisplays#[self.parent.gismanager.disp_idx] + print self.parent.gismanager.disp_idx + def OnItemActivated(self, event): self.currentItem = event.m_itemIndex self.log.write("OnItemActivated: %s\nTopItem: %s\n" % @@ -243,6 +251,7 @@ # probably self.gismanager = parent + print self.gismanager self.win = TestVirtualList(self, log,tablename=table) self.Show() Modified: trunk/grassaddons/gui/wxgui.py =================================================================== --- trunk/grassaddons/gui/wxgui.py 2007-04-03 14:24:38 UTC (rev 417) +++ trunk/grassaddons/gui/wxgui.py 2007-04-03 15:17:49 UTC (rev 418) @@ -124,6 +124,7 @@ #self.panel = wx.Panel(self,-1, style= wx.EXPAND) self.sizer= wx.BoxSizer(wx.VERTICAL) self.cmdsizer = wx.BoxSizer(wx.HORIZONTAL) + self.track = track # do layout self.SetTitle(_("GRASS GIS Manager - wxPython Prototype")) @@ -330,7 +331,7 @@ if mapset == grassenv.env["MAPSET"]: from gui_modules import dbm - self.dbmanager = gui_modules.dbm.AttributeManager(None, -1,"GRASS Attribute Table Manager: %s" % map, size=wx.Size(500,300),table=map) + self.dbmanager = gui_modules.dbm.AttributeManager(self, -1,"GRASS Attribute Table Manager: %s" % map, size=wx.Size(500,300),table=map) def newDisplay(self, event=None): From cepicky at grass.itc.it Tue Apr 3 17:18:15 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Tue Apr 3 17:18:16 2007 Subject: [grass-addons] r419 - trunk/grassaddons/gui/gui_modules Message-ID: <200704031518.l33FIFlm025860@grass.itc.it> Author: cepicky Date: 2007-04-03 17:18:15 +0200 (Tue, 03 Apr 2007) New Revision: 419 Modified: trunk/grassaddons/gui/gui_modules/dbm.py Log: parent for attribute table Modified: trunk/grassaddons/gui/gui_modules/dbm.py =================================================================== --- trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-03 15:17:49 UTC (rev 418) +++ trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-03 15:18:15 UTC (rev 419) @@ -126,7 +126,6 @@ (self.currentItem, self.GetItemText(self.currentItem))) - print self.parent.gismanager if self.parent.gismanager: gism = self.parent.gismanager curr_pg = gism.gm_cb.GetCurrentPage() @@ -251,7 +250,6 @@ # probably self.gismanager = parent - print self.gismanager self.win = TestVirtualList(self, log,tablename=table) self.Show() From cepicky at grass.itc.it Tue Apr 3 17:45:41 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Tue Apr 3 17:45:42 2007 Subject: [grass-addons] r420 - trunk/grassaddons/gui/gui_modules Message-ID: <200704031545.l33Fjf3r026344@grass.itc.it> Author: cepicky Date: 2007-04-03 17:45:41 +0200 (Tue, 03 Apr 2007) New Revision: 420 Modified: trunk/grassaddons/gui/gui_modules/render.py Log: better key-value separation Modified: trunk/grassaddons/gui/gui_modules/render.py =================================================================== --- trunk/grassaddons/gui/gui_modules/render.py 2007-04-03 15:18:15 UTC (rev 419) +++ trunk/grassaddons/gui/gui_modules/render.py 2007-04-03 15:45:41 UTC (rev 420) @@ -75,7 +75,7 @@ self.cmd = "d.rast -o map=%s@%s" % (self.name, self.mapset) for key,value in self.grassLayer.params.iteritems(): - if type(value) == type(""): + if type(value) != type(None): self.cmd += " %s=%s" % \ (key, value) else: @@ -99,7 +99,7 @@ self.cmd = "d.vect map=%s@%s" % (self.name, self.mapset) for key,value in self.grassLayer.params.iteritems(): - if type(value) == type(""): + if type(value) != type(None): self.cmd += " %s=%s" % \ (key, value) else: @@ -678,10 +678,12 @@ layer = MapLayer(type="raster", name=name, mapset=mapset, active=l_active, hidden=l_hidden, opacity=l_opacity, dispcmd=dispcmd) + layer.id = len(self.layers)-1 # add maplayer to the list of layers self.layers.append(layer) + if l_render: if not layer.Render(): sys.stderr.write("Could not render layer <%s@%s>\n" % \ @@ -721,6 +723,7 @@ coordsinmapunits = coordsinmapunits) self.layers.append(layer) + layer.id = len(self.layers)-1 if l_render: if not layer.Render(): @@ -761,6 +764,7 @@ active=l_active, hidden=l_hidden, opacity=l_opacity) self.layers.append(maplayer) + maplayer.id = len(self.layers)-1 if l_render: if not maplayer.Render(): From cepicky at grass.itc.it Tue Apr 3 17:54:01 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Tue Apr 3 17:54:01 2007 Subject: [grass-addons] r421 - trunk/grassaddons/gui/gui_modules Message-ID: <200704031554.l33Fs1xb026388@grass.itc.it> Author: cepicky Date: 2007-04-03 17:54:01 +0200 (Tue, 03 Apr 2007) New Revision: 421 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py Log: making Map to attribute of mapdisplay Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-03 15:45:41 UTC (rev 420) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-03 15:54:01 UTC (rev 421) @@ -158,10 +158,11 @@ def __init__(self, parent, id, pos = wx.DefaultPosition, size = wx.DefaultSize, - style=wx.NO_FULL_REPAINT_ON_RESIZE): + style=wx.NO_FULL_REPAINT_ON_RESIZE,map=Map): wx.Window.__init__(self, parent, id, pos, size, style) self.parent = parent + self.Map = map # # Flags @@ -352,20 +353,20 @@ """ # set size of the input image - Map.width, Map.height = self.GetClientSize() + self.Map.width, self.Map.height = self.GetClientSize() # Make new off screen bitmap: this bitmap will always have the # current drawing in it, so it can be used to save the image to # a file, or whatever. - self._Buffer = wx.EmptyBitmap(Map.width, Map.height) + self._Buffer = wx.EmptyBitmap(self.Map.width, self.Map.height) # get the image to be rendered self.img = self.GetImage() # update map display - if self.img and Map.width + Map.height > 0: # scale image during resize - self.img = self.img.Scale(Map.width, Map.height) + if self.img and self.Map.width + self.Map.height > 0: # scale image during resize + self.img = self.img.Scale(self.Map.width, self.Map.height) self.render = False self.UpdateMap() @@ -396,8 +397,8 @@ Converts overlay files to wx.Image """ ovlist = [] - if Map.ovlist: - for ovlfile in Map.ovlist: + if self.Map.ovlist: + for ovlfile in self.Map.ovlist: if os.path.isfile(ovlfile) and os.path.getsize(ovlfile): img = wx.Image(ovlfile, wx.BITMAP_TYPE_ANY) ovlist.append(img) @@ -409,9 +410,9 @@ """ Converts files to wx.Image """ - if Map.mapfile and os.path.isfile(Map.mapfile) and \ - os.path.getsize(Map.mapfile): - img = wx.Image(Map.mapfile, wx.BITMAP_TYPE_ANY) + if self.Map.mapfile and os.path.isfile(self.Map.mapfile) and \ + os.path.getsize(self.Map.mapfile): + img = wx.Image(self.Map.mapfile, wx.BITMAP_TYPE_ANY) else: img = None @@ -434,8 +435,8 @@ if self.render: # render new map images - Map.width, Map.height = self.GetClientSize() - self.mapfile = Map.Render(force=self.render) + self.Map.width, self.Map.height = self.GetClientSize() + self.mapfile = self.Map.Render(force=self.render) self.img = self.GetImage() self.resize = False @@ -464,8 +465,8 @@ # update statusbar self.parent.statusbar.SetStatusText("Extent: %d,%d : %d,%d" % - (Map.region["w"], Map.region["e"], - Map.region["n"], Map.region["s"]), 0) + (self.Map.region["w"], self.Map.region["e"], + self.Map.region["n"], self.Map.region["s"]), 0) def EraseMap(self): """ @@ -621,9 +622,9 @@ elif wheel != 0: # zoom 1/2 of the screen - begin = [Map.width/4, Map.height/4] - end = [Map.width - Map.width/4, - Map.height - Map.height/4] + begin = [self.Map.width/4, self.Map.height/4] + end = [self.Map.width - self.Map.width/4, + self.Map.height - self.Map.height/4] elif event.RightDown(): x,y = event.GetPositionTuple()[:] @@ -650,8 +651,8 @@ Input : x, y Output: int x, int y """ - newx = Map.region['w'] + x * Map.region["ewres"] - newy = Map.region['n'] - y * Map.region["nsres"] + newx = self.Map.region['w'] + x * self.Map.region["ewres"] + newy = self.Map.region['n'] - y * self.Map.region["nsres"] return newx, newy @@ -681,23 +682,23 @@ -x1*2, -y1*2) newreg['e'], newreg['s'] = self.Pixel2Cell( - Map.width+2*(Map.width-x2), - Map.height+2*(Map.height-y2)) + self.Map.width+2*(self.Map.width-x2), + self.Map.height+2*(self.Map.height-y2)) # pan elif zoomtype == 0: newreg['w'], newreg['n'] = self.Pixel2Cell( x1-x2, y1-y2) newreg['e'], newreg['s'] = self.Pixel2Cell( - Map.width+x1-x2, - Map.height+y1-y2) + self.Map.width+x1-x2, + self.Map.height+y1-y2) # if new region has been calculated, set the values if newreg : - Map.region['n'] = newreg['n'] - Map.region['s'] = newreg['s'] - Map.region['e'] = newreg['e'] - Map.region['w'] = newreg['w'] + self.Map.region['n'] = newreg['n'] + self.Map.region['s'] = newreg['s'] + self.Map.region['e'] = newreg['e'] + self.Map.region['w'] = newreg['w'] #class DrawWindow(BufferedWindow): @@ -831,6 +832,7 @@ # most of the thime, this will be the gis manager self.gismanager = parent + self.Map = Map # # Set the size @@ -861,24 +863,24 @@ self.statusbar = self.CreateStatusBar(number=2, style=0) self.statusbar.SetStatusWidths([-2, -1]) map_frame_statusbar_fields = ["Extent: %d,%d : %d,%d" % - (Map.region["w"], Map.region["e"], - Map.region["n"], Map.region["s"]), + (self.Map.region["w"], self.Map.region["e"], + self.Map.region["n"], self.Map.region["s"]), "%s,%s" %(None, None)] for i in range(len(map_frame_statusbar_fields)): self.statusbar.SetStatusText(map_frame_statusbar_fields[i], i) # d.barscale overlay added to rendering overlay list - Map.addOverlay(type=0, command='d.barscale', l_active=True, l_render=False) + self.Map.addOverlay(type=0, command='d.barscale', l_active=True, l_render=False) # d.barscale overlay added to rendering overlay list as placeholder for d.legend - Map.addOverlay(type=1, command='d.barscale', l_active=True, l_render=False) + self.Map.addOverlay(type=1, command='d.barscale', l_active=True, l_render=False) # # Init map display # self.InitDisplay() # initialize region values # self.MapWindow = DrawWindow(self) # initialize buffered DC - self.MapWindow = BufferedWindow(self, id = wx.ID_ANY) # initialize buffered DC + self.MapWindow = BufferedWindow(self, id = wx.ID_ANY,map=self.Map) # initialize buffered DC self.MapWindow.Bind(wx.EVT_MOTION, self.OnMotion) # decoration overlays @@ -917,14 +919,14 @@ * digit """ if name == "map": - self.maptoolbar = toolbars.MapToolbar(self, Map) + self.maptoolbar = toolbars.MapToolbar(self, self.Map) self._mgr.AddPane(self.maptoolbar.toolbar, wx.aui.AuiPaneInfo(). Name("maptoolbar").Caption("Map Toolbar"). ToolbarPane().Top().LeftDockable(False).RightDockable(False). BottomDockable(True).CloseButton(False)) if name == "digit": - self.digittoolbar = toolbars.DigitToolbar(self,Map) + self.digittoolbar = toolbars.DigitToolbar(self,self.Map) self._mgr.AddPane(self.digittoolbar.toolbar, wx.aui.AuiPaneInfo(). Name("digittoolbar").Caption("Digit Toolbar"). ToolbarPane().Top().LeftDockable(False).RightDockable(False). @@ -936,12 +938,12 @@ Initialize map display, set dimensions and map region """ self.width, self.height = self.GetClientSize() - Map.geom = self.width, self.height - Map.GetRegion() + self.Map.geom = self.width, self.height + self.Map.GetRegion() #FIXME #This was Map.getResolution(). #I'm guessing at the moment that this is replaced by Map.SetRegion() - Map.SetRegion() + self.Map.SetRegion() def OnFocus(self, event): """ @@ -1059,8 +1061,8 @@ """ Zoom to region """ - Map.getRegion() - Map.getResolution() + self.Map.getRegion() + self.Map.getResolution() self.UpdateMap() # self.draw(dc) event.Skip() @@ -1069,10 +1071,10 @@ """ Align region """ - if not Map.alignRegion: - Map.alignRegion = True + if not self.Map.alignRegion: + self.Map.alignRegion = True else: - Map.alignRegion = False + self.Map.alignRegion = False event.Skip() def SaveToFile(self, event): @@ -1092,7 +1094,7 @@ """ Window closed """ - Map.Clean() + self.Map.Clean() self.Destroy() #close associated controls book page @@ -1115,7 +1117,7 @@ """ returns the current instance of render.Map() """ - return Map + return self.Map def OnQuery(self, event): """ @@ -1263,7 +1265,7 @@ # Reset comand and rendering options in render.Map. Always render decoration. # Showing/hiding handled by PseudoDC - Map.changeOverlay(type=type, command=dcmd, l_active=True, l_render=False) + self.Map.changeOverlay(type=type, command=dcmd, l_active=True, l_render=False) self.params[type] = params # end of class MapFrame From barton at grass.itc.it Tue Apr 3 17:58:49 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Tue Apr 3 17:58:51 2007 Subject: [grass-addons] r422 - trunk/grassaddons/gui/gui_modules Message-ID: <200704031558.l33FwnfA026421@grass.itc.it> Author: barton Date: 2007-04-03 17:58:40 +0200 (Tue, 03 Apr 2007) New Revision: 422 Modified: trunk/grassaddons/gui/gui_modules/wxgui_utils.py Log: Fixed checking/unchecking for layers inside groups. Modified: trunk/grassaddons/gui/gui_modules/wxgui_utils.py =================================================================== --- trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-03 15:54:01 UTC (rev 421) +++ trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-03 15:58:40 UTC (rev 422) @@ -298,10 +298,10 @@ childchecked = False else: childchecked = child.IsChecked() - self.changeChecked(child, childchecked) + self.Map.changeActive(child, childchecked) child = self.GetNextChild(layer, cookie)[0] else: - self.changeChecked(layer, checked) + self.Map.changeActive(layer, checked) def onCmdChanged(self, event): @@ -367,6 +367,7 @@ delete original at old position """ + self.drag = True # Make sure this memeber exists. try: old = self.dragItem @@ -395,10 +396,12 @@ new = self.PrependItem(self.root, text=self.saveitem['text'], \ ct_type=1, wnd=newctrl, image=self.saveitem['image'], \ data=self.saveitem['data']) - elif (flag & wx.TREE_HITTEST_BELOW) or (flag & wx.TREE_HITTEST_NOWHERE): + elif (flag & wx.TREE_HITTEST_BELOW) or (flag & wx.TREE_HITTEST_NOWHERE) \ + or (flag & wx.TREE_HITTEST_TOLEFT) or (flag & wx.TREE_HITTEST_TORIGHT): new = self.AppendItem(self.root, text=self.saveitem['text'], \ ct_type=1, wnd=newctrl, image=self.saveitem['image'], \ data=self.saveitem['data']) + else: if not event.GetItem(): return @@ -409,6 +412,7 @@ new = self.AppendItem(parent, text=self.saveitem['text'], \ ct_type=1, wnd=newctrl, image=self.saveitem['image'], \ data=self.saveitem['data']) + self.Expand(afteritem) else: parent = self.GetItemParent(afteritem) new = self.InsertItem(parent, afteritem, text=self.saveitem['text'], \ @@ -422,7 +426,7 @@ newctrl.SetValue(self.saveitem['windval']) # update lookup dictionary in render.Map - if self.saveitem['type'] != 'group': + if self.layertype[new] != 'group': self.Map.updateLookup(old, new) # delete layer at original position @@ -468,14 +472,15 @@ def reorderLayers(self): """ add commands from data associated with - any valid and checked layers to layer list + any valid layers (checked or not) to layer list in order to + match layers in layer tree """ # make a list of visible layers treelayers = [] vislayer = self.GetFirstVisibleItem() for item in range(0,self.GetCount()): - if self.IsItemChecked(vislayer) and self.layertype[vislayer] != 'group': + if self.layertype[vislayer] != 'group': treelayers.append(vislayer) if self.GetNextVisible(vislayer) == None: break @@ -484,13 +489,6 @@ treelayers.reverse() self.Map.reorderLayers(treelayers) - def changeOpacity(self, layer, opacity): - self.Map.changeOpacity(layer, opacity) - - def changeChecked(self, layer, check): - if self.layertype[layer] != 'group': - self.Map.changeActive(layer, check) - def changeLayer(self, layer): if self.layertype[layer] == 'command': if self.GetItemWindow(layer).GetValue() != None: From cepicky at grass.itc.it Tue Apr 3 18:18:53 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Tue Apr 3 18:18:54 2007 Subject: [grass-addons] r423 - trunk/grassaddons/gui/gui_modules Message-ID: <200704031618.l33GIrGR026523@grass.itc.it> Author: cepicky Date: 2007-04-03 18:18:53 +0200 (Tue, 03 Apr 2007) New Revision: 423 Modified: trunk/grassaddons/gui/gui_modules/dbm.py Log: drawing selected object should work .. but does not Modified: trunk/grassaddons/gui/gui_modules/dbm.py =================================================================== --- trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-03 15:58:40 UTC (rev 422) +++ trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-03 16:18:53 UTC (rev 423) @@ -43,11 +43,12 @@ #---------------------------------------------------------------------- class TestVirtualList(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.ColumnSorterMixin): - def __init__(self, parent,log,tablename): + def __init__(self, parent,log,tablename,mapset=None): wx.ListCtrl.__init__( self, parent, -1, style=wx.LC_REPORT|wx.LC_VIRTUAL|wx.LC_HRULES|wx.LC_VRULES) self.log=log self.tablename = tablename + self.mapset = mapset self.columns = [] self.columnNumber = 0 self.parent = parent @@ -127,12 +128,22 @@ self.GetItemText(self.currentItem))) if self.parent.gismanager: + gism = self.parent.gismanager curr_pg = gism.gm_cb.GetCurrentPage() disp_idx = gism.track.Track().GetDisp_idx(curr_pg) - print self.parent.gismanager.mapdisplays#[self.parent.gismanager.disp_idx] - print self.parent.gismanager.disp_idx + mapdisp = self.parent.gismanager.mapdisplays[disp_idx] + map = gism.maptree.Map + print map.GetListOfLayers() + try: + map.RemoveLayer(id=self.querylayer.id) + except: + pass + cat = self.GetItemText(self.currentItem) + self.querylayer = map.addLayer(item=None, command="d.vect map=%s@%s color=yellow fcolor=yellow cats=%s width=3" % (self.tablename, self.mapset, cat), l_active=True, + l_hidden=False, l_opacity=1, l_render=False) + mapdisp.ReDraw(None) def OnItemActivated(self, event): self.currentItem = event.m_itemIndex @@ -240,7 +251,7 @@ class AttributeManager(wx.Frame): - def __init__(self, parent, id, title, size, style = wx.DEFAULT_FRAME_STYLE, table=None ): + def __init__(self, parent, id, title, size, style = wx.DEFAULT_FRAME_STYLE, table=None,mapset=None ): wx.Frame.__init__(self, parent, id, title, size=size, style=style) @@ -251,7 +262,7 @@ # probably self.gismanager = parent - self.win = TestVirtualList(self, log,tablename=table) + self.win = TestVirtualList(self, log,tablename=table,mapset=mapset) self.Show() def main(argv=None): From cepicky at grass.itc.it Tue Apr 3 18:39:11 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Tue Apr 3 18:39:12 2007 Subject: [grass-addons] r424 - in trunk/grassaddons/gui: . gui_modules Message-ID: <200704031639.l33GdBeQ026632@grass.itc.it> Author: cepicky Date: 2007-04-03 18:39:10 +0200 (Tue, 03 Apr 2007) New Revision: 424 Modified: trunk/grassaddons/gui/gui_modules/dbm.py trunk/grassaddons/gui/gui_modules/render.py trunk/grassaddons/gui/wxgui.py Log: displaying objects works Modified: trunk/grassaddons/gui/gui_modules/dbm.py =================================================================== --- trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-03 16:18:53 UTC (rev 423) +++ trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-03 16:39:10 UTC (rev 424) @@ -52,6 +52,7 @@ self.columns = [] self.columnNumber = 0 self.parent = parent + self.qlayer = None #adding some attributes (colourful background for each item rows) self.attr1 = wx.ListItemAttr() @@ -127,6 +128,7 @@ (self.currentItem, self.GetItemText(self.currentItem))) + # show us the result in map display if self.parent.gismanager: gism = self.parent.gismanager @@ -135,13 +137,11 @@ mapdisp = self.parent.gismanager.mapdisplays[disp_idx] map = gism.maptree.Map - print map.GetListOfLayers() - try: - map.RemoveLayer(id=self.querylayer.id) - except: - pass + if self.qlayer: + map.RemoveLayer(id=map.layers.index(self.qlayer)) + cat = self.GetItemText(self.currentItem) - self.querylayer = map.addLayer(item=None, command="d.vect map=%s@%s color=yellow fcolor=yellow cats=%s width=3" % (self.tablename, self.mapset, cat), l_active=True, + self.qlayer = map.addLayer(item=None, command="d.vect map=%s@%s color=yellow fcolor=yellow cats=%s width=3" % (self.tablename, self.mapset, cat), l_active=True, l_hidden=False, l_opacity=1, l_render=False) mapdisp.ReDraw(None) Modified: trunk/grassaddons/gui/gui_modules/render.py =================================================================== --- trunk/grassaddons/gui/gui_modules/render.py 2007-04-03 16:18:53 UTC (rev 423) +++ trunk/grassaddons/gui/gui_modules/render.py 2007-04-03 16:39:10 UTC (rev 424) @@ -855,7 +855,7 @@ return self.layers[-1] - def delLayer(self, item): + def delLayer(self, item, name=None): """ Removes layer from list of layers, defined by layer tree item ID @@ -866,7 +866,6 @@ Returns: Removed layer on success or None """ - layer = self.lookup[item] if layer in self.layers: if layer.mapfile: Modified: trunk/grassaddons/gui/wxgui.py =================================================================== --- trunk/grassaddons/gui/wxgui.py 2007-04-03 16:18:53 UTC (rev 423) +++ trunk/grassaddons/gui/wxgui.py 2007-04-03 16:39:10 UTC (rev 424) @@ -331,7 +331,9 @@ if mapset == grassenv.env["MAPSET"]: from gui_modules import dbm - self.dbmanager = gui_modules.dbm.AttributeManager(self, -1,"GRASS Attribute Table Manager: %s" % map, size=wx.Size(500,300),table=map) + self.dbmanager = gui_modules.dbm.AttributeManager(self, + -1,"GRASS Attribute Table Manager: %s" % map, + size=wx.Size(500,300),table=map,mapset=mapset) def newDisplay(self, event=None): From cepicky at grass.itc.it Tue Apr 3 18:47:21 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Tue Apr 3 18:47:24 2007 Subject: [grass-addons] r425 - trunk/grassaddons/gui/screenshots Message-ID: <200704031647.l33GlLq0026704@grass.itc.it> Author: cepicky Date: 2007-04-03 18:47:21 +0200 (Tue, 03 Apr 2007) New Revision: 425 Added: trunk/grassaddons/gui/screenshots/dbmanager-01.png Log: new screenshot Added: trunk/grassaddons/gui/screenshots/dbmanager-01.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/screenshots/dbmanager-01.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream From barton at grass.itc.it Wed Apr 4 07:50:37 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Wed Apr 4 07:50:40 2007 Subject: [grass-addons] r426 - trunk/grassaddons/gui/gui_modules Message-ID: <200704040550.l345obCW006036@grass.itc.it> Author: barton Date: 2007-04-04 07:48:57 +0200 (Wed, 04 Apr 2007) New Revision: 426 Modified: trunk/grassaddons/gui/gui_modules/render.py Log: Fix bug in delete layer method. Modified: trunk/grassaddons/gui/gui_modules/render.py =================================================================== --- trunk/grassaddons/gui/gui_modules/render.py 2007-04-03 16:47:21 UTC (rev 425) +++ trunk/grassaddons/gui/gui_modules/render.py 2007-04-04 05:48:57 UTC (rev 426) @@ -866,6 +866,7 @@ Returns: Removed layer on success or None """ + layer = self.lookup[item] if layer in self.layers: if layer.mapfile: From barton at grass.itc.it Wed Apr 4 07:53:38 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Wed Apr 4 07:53:48 2007 Subject: [grass-addons] r427 - trunk/grassaddons/gui/gui_modules Message-ID: <200704040553.l345rcQM006088@grass.itc.it> Author: barton Date: 2007-04-04 07:53:18 +0200 (Wed, 04 Apr 2007) New Revision: 427 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py Log: Fix bug in map erase routine (Draw method) Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-04 05:48:57 UTC (rev 426) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-04 05:53:18 UTC (rev 427) @@ -276,11 +276,17 @@ else: drawid = wx.NewId() - self.ovlcoords[drawid] = coords - self.ovlchk[drawid] = True - pdc.SetId(drawid) - self.select[drawid] = False + if drawid: + self.ovlcoords[drawid] = coords + self.ovlchk[drawid] = True + pdc.SetId(drawid) + self.select[drawid] = False + pdc.BeginDrawing() + pdc.SetBackground(wx.Brush(self.GetBackgroundColour())) + pdc.Clear() + self.Refresh() + if pdctype == 'clear': # erase the display pdc.EndDrawing() return @@ -594,8 +600,8 @@ self.parent.QueryMap(map,mapset,type,east,north) else: print "Quering without gis manager not implemented yet" - + elif self.dragid: self.Refresh() self.Update() From barton at grass.itc.it Wed Apr 4 07:58:31 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Wed Apr 4 07:58:44 2007 Subject: [grass-addons] r428 - in trunk/grassaddons/gui: . gui_modules Message-ID: <200704040558.l345wV6o006142@grass.itc.it> Author: barton Date: 2007-04-04 07:58:00 +0200 (Wed, 04 Apr 2007) New Revision: 428 Modified: trunk/grassaddons/gui/gui_modules/dbm.py trunk/grassaddons/gui/wxgui.py Log: If specific icons and icon sizes specified in map layer, the selection image will match these icons. Also changed the way that information about the selected map is obtained to a more reliable algorithm that looks at the command associated with the layer instead of the layer text (which can be user edited). Modified: trunk/grassaddons/gui/gui_modules/dbm.py =================================================================== --- trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-04 05:53:18 UTC (rev 427) +++ trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-04 05:58:00 UTC (rev 428) @@ -26,7 +26,7 @@ import wx.lib.mixins.listctrl as listmix import sys,os - + #---------------------------------------------------------------------- class Log: r"""\brief Needed by the wxdemos. @@ -43,12 +43,18 @@ #---------------------------------------------------------------------- class TestVirtualList(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.ColumnSorterMixin): - def __init__(self, parent,log,tablename,mapset=None): + def __init__(self, parent,log,tablename,mapset=None,pointdata=None): wx.ListCtrl.__init__( self, parent, -1, style=wx.LC_REPORT|wx.LC_VIRTUAL|wx.LC_HRULES|wx.LC_VRULES) self.log=log self.tablename = tablename self.mapset = mapset + self.icon = '' + self.pointsize = '' + + self.icon = pointdata[0] + self.pointsize = pointdata[1] + self.columns = [] self.columnNumber = 0 self.parent = parent @@ -104,7 +110,7 @@ break self.SetItemCount(len(self.itemDataMap)) - + #mixins listmix.ListCtrlAutoWidthMixin.__init__(self) listmix.ColumnSorterMixin.__init__(self, 3) @@ -117,7 +123,11 @@ self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated) self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselected) self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick) + self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) + def OnCloseWindow(self, event): + if self.qlayer: map.delLayer(item='qlayer') + def OnColClick(self,event): self.columnNumber = event.GetColumn() event.Skip() @@ -137,11 +147,15 @@ mapdisp = self.parent.gismanager.mapdisplays[disp_idx] map = gism.maptree.Map - if self.qlayer: - map.RemoveLayer(id=map.layers.index(self.qlayer)) - + if self.qlayer: map.delLayer(item='qlayer') + cat = self.GetItemText(self.currentItem) - self.qlayer = map.addLayer(item=None, command="d.vect map=%s@%s color=yellow fcolor=yellow cats=%s width=3" % (self.tablename, self.mapset, cat), l_active=True, + + cmd = "d.vect map=%s@%s color=yellow fcolor=yellow cats=%s width=3" % (self.tablename, self.mapset, cat) + if self.icon: cmd = cmd +" icon=%s" % (self.icon) + if self.pointsize: cmd = cmd + " size=%s" % (self.pointsize) + + self.qlayer = map.addLayer(item='qlayer', command=cmd, l_active=True, l_hidden=False, l_opacity=1, l_render=False) mapdisp.ReDraw(None) @@ -195,7 +209,7 @@ # for i in range(len(items)): # items[i] = str(items[i]) self.itemIndexMap = items - + # redraw the list self.Refresh() @@ -251,18 +265,19 @@ class AttributeManager(wx.Frame): - def __init__(self, parent, id, title, size, style = wx.DEFAULT_FRAME_STYLE, table=None,mapset=None ): + def __init__(self, parent, id, title, size, style = wx.DEFAULT_FRAME_STYLE, + table=None,mapset=None,pointdata=None): wx.Frame.__init__(self, parent, id, title, size=size, style=style) self.CreateStatusBar(1) log=Log(self) - + # probably self.gismanager = parent - self.win = TestVirtualList(self, log,tablename=table,mapset=mapset) + self.win = TestVirtualList(self, log,tablename=table,mapset=mapset,pointdata=pointdata) self.Show() def main(argv=None): Modified: trunk/grassaddons/gui/wxgui.py =================================================================== --- trunk/grassaddons/gui/wxgui.py 2007-04-04 05:53:18 UTC (rev 427) +++ trunk/grassaddons/gui/wxgui.py 2007-04-04 05:58:00 UTC (rev 428) @@ -323,17 +323,41 @@ ) def ShowAttributeTable(self,event): - mapsel = self.maptree.GetSelection() +# mapsel = self.maptree.GetSelection() + dcmd = self.maptree.GetPyData(self.maptree.GetSelection())[0] + mapname = map = mapset = size = icon = None + for item in dcmd.split(' '): + if 'map=' in item: + mapname = item.split('=')[1] + if '@' in mapname: + map = mapname.split('@')[0] + mapset = mapname.split('@')[1] + else: + map = mapname + elif 'size=' in item: + size = item.split('=')[1] + print 'size=',size + elif 'icon=' in item: + icon = item.split('=')[1] + print 'icon=',icon - name = mapsel.GetText() - if name.find("@") >-1: - map,mapset = name.strip().split("@") - - if mapset == grassenv.env["MAPSET"]: - from gui_modules import dbm - self.dbmanager = gui_modules.dbm.AttributeManager(self, + pointdata = (icon,size) +# +# name = mapsel.GetText() +# if name.find("@") >-1: +# map,mapset = name.strip().split("@") +# if mapset == grassenv.env["MAPSET"]: +# from gui_modules import dbm +# self.dbmanager = gui_modules.dbm.AttributeManager(self, +# -1,"GRASS Attribute Table Manager: %s" % map, +# size=wx.Size(500,300),table=map,mapset=mapset) + + if mapset == grassenv.env["MAPSET"]: + from gui_modules import dbm + self.dbmanager = gui_modules.dbm.AttributeManager(self, -1,"GRASS Attribute Table Manager: %s" % map, - size=wx.Size(500,300),table=map,mapset=mapset) + size=wx.Size(500,300),table=map,mapset=mapset, + pointdata=pointdata) def newDisplay(self, event=None): From barton at grass.itc.it Wed Apr 4 08:04:57 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Wed Apr 4 08:05:04 2007 Subject: [grass-addons] r429 - trunk/grassaddons/gui Message-ID: <200704040604.l3464vY7006261@grass.itc.it> Author: barton Date: 2007-04-04 08:04:17 +0200 (Wed, 04 Apr 2007) New Revision: 429 Modified: trunk/grassaddons/gui/wxgui.py Log: Tests for vector file before opening attribute management table Modified: trunk/grassaddons/gui/wxgui.py =================================================================== --- trunk/grassaddons/gui/wxgui.py 2007-04-04 05:58:00 UTC (rev 428) +++ trunk/grassaddons/gui/wxgui.py 2007-04-04 06:04:17 UTC (rev 429) @@ -323,7 +323,12 @@ ) def ShowAttributeTable(self,event): -# mapsel = self.maptree.GetSelection() + + maptype = self.maptree.layertype[self.maptree.GetSelection()] + if maptype != 'vector': + print 'Attribute management only available for vector files' + return + dcmd = self.maptree.GetPyData(self.maptree.GetSelection())[0] mapname = map = mapset = size = icon = None for item in dcmd.split(' '): @@ -342,15 +347,6 @@ print 'icon=',icon pointdata = (icon,size) -# -# name = mapsel.GetText() -# if name.find("@") >-1: -# map,mapset = name.strip().split("@") -# if mapset == grassenv.env["MAPSET"]: -# from gui_modules import dbm -# self.dbmanager = gui_modules.dbm.AttributeManager(self, -# -1,"GRASS Attribute Table Manager: %s" % map, -# size=wx.Size(500,300),table=map,mapset=mapset) if mapset == grassenv.env["MAPSET"]: from gui_modules import dbm From barton at grass.itc.it Wed Apr 4 08:06:57 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Wed Apr 4 08:07:00 2007 Subject: [grass-addons] r430 - trunk/grassaddons/gui Message-ID: <200704040606.l3466vMs006285@grass.itc.it> Author: barton Date: 2007-04-04 08:05:25 +0200 (Wed, 04 Apr 2007) New Revision: 430 Modified: trunk/grassaddons/gui/wxgui.py Log: Removed debugging code Modified: trunk/grassaddons/gui/wxgui.py =================================================================== --- trunk/grassaddons/gui/wxgui.py 2007-04-04 06:04:17 UTC (rev 429) +++ trunk/grassaddons/gui/wxgui.py 2007-04-04 06:05:25 UTC (rev 430) @@ -341,10 +341,8 @@ map = mapname elif 'size=' in item: size = item.split('=')[1] - print 'size=',size elif 'icon=' in item: icon = item.split('=')[1] - print 'icon=',icon pointdata = (icon,size) From neteler at grass.itc.it Wed Apr 4 09:52:50 2007 From: neteler at grass.itc.it (neteler@grass.itc.it) Date: Wed Apr 4 09:53:22 2007 Subject: [grass-addons] r431 - in trunk/grassaddons: . v.curvature Message-ID: <200704040752.l347qoZP007419@grass.itc.it> Author: neteler Date: 2007-04-04 09:51:03 +0200 (Wed, 04 Apr 2007) New Revision: 431 Added: trunk/grassaddons/v.curvature/ trunk/grassaddons/v.curvature/Makefile trunk/grassaddons/v.curvature/description.html trunk/grassaddons/v.curvature/main.c Log: v.curvature from Radim Blazek/ITC-irst Added: trunk/grassaddons/v.curvature/Makefile =================================================================== --- trunk/grassaddons/v.curvature/Makefile (rev 0) +++ trunk/grassaddons/v.curvature/Makefile 2007-04-04 07:51:03 UTC (rev 431) @@ -0,0 +1,13 @@ +MODULE_TOPDIR = ../.. + +PGM = v.curvature + +LIBES = $(VECTLIB) $(GISLIB) +DEPENDENCIES= $(VECTDEP) $(GISDEP) +EXTRA_INC = $(VECT_INC) +EXTRA_CFLAGS = $(VECT_CFLAGS) + +include $(MODULE_TOPDIR)/include/Make/Module.make + +default: cmd + Added: trunk/grassaddons/v.curvature/description.html =================================================================== --- trunk/grassaddons/v.curvature/description.html (rev 0) +++ trunk/grassaddons/v.curvature/description.html 2007-04-04 07:51:03 UTC (rev 431) @@ -0,0 +1,49 @@ +

DESCRIPTION

+ +v.curvature calculates average curvature along a segment +given by from/to distance measured along the line specified by +category. +

+For individual straight parts of the line, the average curvature as +calculated from directions of adjacent parts. An average of specified +segment is then calculated as average from curvatures of line parts +(lengths of line parts overlapped by specified segment are taken into +acount). +

+This method may be used only if line data digitized with appropriate +accuracy, where 'appropriate' depend on an application we want to use +it for and on the length of the segment we want to calculate the +curvature for. +

+If user needs curvature for too small segments compared to the density +of vertices on lines, the lines should be smoothed beforehand with +some other module (this approach is preferred to internal smoothing +(interpolating) within v.curvature, because automatic smoothing may +often result in unexpected, unwanted shapes, and user should see which +data are really processed). +

+With the 'segment' option, the module reads from 'stdin': +

+ +Output is written to 'stdout' in format: +
+  segment_id average_curvature average_radius
+
+ +

AUTHOR

+ +Radim Blazek, ITC-Irst, Trento, Italy + +

Last changed: $Date: 2006/01/02 14:44:51 $ Added: trunk/grassaddons/v.curvature/main.c =================================================================== --- trunk/grassaddons/v.curvature/main.c (rev 0) +++ trunk/grassaddons/v.curvature/main.c 2007-04-04 07:51:03 UTC (rev 431) @@ -0,0 +1,358 @@ +/**************************************************************** +* +* MODULE: v.curvature +* +* AUTHOR(S): Radim Blazek +* +* PURPOSE: Calculate something similar to curvature of line +* in specified segment. Module reads from stdin: +* +* segment_id line_category from to +* +* segment_id - identifier for one segment we need curvature for +* line_category - category of line the segment is on +* from - distance of segment start from the beginnig of the line +* to - distance of end of segment from the beginnig of the line +* +* Note that if segment limits may exceed the line, segment is cuted +* by line ends. +* +* Output is written to stdout in format: +* +* segment_id average_curvature average_radius +* +* +* COPYRIGHT: (C) 2001 by the GRASS Development Team +* +* This program is free software under the +* GNU General Public License (>=v2). +* Read the file COPYING that comes with GRASS +* for details. +* +****************************************************************/ +#include +#include +#include +#include +#include +#include +#include + +#define OPTION_SEGMENT 1 +#define OPTION_LINES 2 + + +double calc_curv (struct line_pnts *Points, int part); +double part_length (struct line_pnts *Points, int part); + +int +main (int argc, char *argv[]) +{ + int i, row; + char buf[1024]; + struct Map_info Map; + struct line_pnts *Points; + struct line_cats *Cats; + int field, type, ctype, action; + int id, cat, ccat, part, end; + double from, to, rad; + double line_len, seg_len, calc_len, part_len, rest_len, dist, len; + double x, y, z, dx, dy, dz; + double cur, curva; + int field_index, cinlines; + struct GModule *module; + struct Option *map_opt, *type_opt, *field_opt, *act_opt; + struct Flag *abs_flag; + + module = G_define_module(); + module->keywords = _("vector, segment, curvature"); + module->description = _("Calculate curvature. With segment option it reads from standard input:\n" + "segment_id line_category from to\n" + "and writes to standard output:\n" + "segment_id average_curvature average_radius\n" + "If a segment given runs outside the line, it is cut. " + "If more lines with the same category " + "are found, curvature is not calculated and warning is printed."); + + map_opt = G_define_standard_option(G_OPT_V_INPUT); + map_opt->key = "map"; + + type_opt = G_define_standard_option(G_OPT_V_TYPE) ; + type_opt->options = "line,boundary"; + type_opt->answer = "line"; + + field_opt = G_define_standard_option(G_OPT_V_FIELD); + + act_opt = G_define_option() ; + act_opt->key = "option" ; + act_opt->type = TYPE_STRING ; + act_opt->required = NO ; + act_opt->multiple = NO; + act_opt->answer = "segment" ; + act_opt->options = "segment,lines"; + act_opt->description= _("Option"); + act_opt->descriptions= _("segment; reads segments from standard input;" + "lines; calculate curvature for all sements of all lines"); + + abs_flag = G_define_flag (); + abs_flag->key = 'a'; + abs_flag->description = _("Calculate average from absolute values " + "(if not used, segment over 2 successive contrary " + "curves can result in curvature near 0)."); + + G_gisinit(argv[0]); + if (G_parser (argc, argv)) + exit(EXIT_FAILURE); + + field = atoi( field_opt->answer ); + + i = 0; + ctype = 0; + while (type_opt->answers[i]) + { + switch ( type_opt->answers[i][0] ) + { + case 'l': + ctype |= GV_LINE; + break; + case 'b': + ctype |= GV_BOUNDARY; + break; + } + i++; + } + + action = OPTION_SEGMENT; + if ( act_opt->answer[0] == 'l' ) + action = OPTION_LINES; + + Points = Vect_new_line_struct (); + Cats = Vect_new_cats_struct (); + + /* open input vector */ + Vect_set_open_level (2); + Vect_open_old (&Map, map_opt->answer, ""); + + cinlines = Vect_cidx_get_type_count ( &Map, field, ctype); + + if ( cinlines == 0 ) + { + G_warning ( _("No lines in layer %d"), field ); + } + + if ( action == OPTION_SEGMENT && cinlines > 0 ) { + int idx, line, type, cincats, dummy1, dummy2; + + field_index = Vect_cidx_get_field_index ( &Map, field ); + cincats = Vect_cidx_get_num_cats_by_index ( &Map, field_index ); + G_debug ( 3, "cincats = %d", cincats ); + + row = 0; + while ( fgets(buf,1024,stdin) != NULL ) { + row++; + if ( sscanf(buf, "%d %d %lf %lf", &id, &ccat, &from, &to) != 4 ) { + G_warning (_("Incorrect format on row %d: %s"), row, buf); + continue; + } + + G_debug (2, "read: %d %d %f %f", id, ccat, from, to); + + /* Find line by category */ + idx = Vect_cidx_find_next ( &Map, field_index, ccat, ctype, 0, &type, &line ); + + if ( idx < 0 ) { + G_warning ( _("Line with category %d not found"), ccat ); + fprintf (stdout, "%d - -\n", id); + continue; + } + + if ( (idx + 1 < cincats) && + Vect_cidx_find_next ( &Map, field_index, ccat, ctype, idx+1, &dummy1, &dummy2 ) >= 0 ) + { + /* Second line found */ + G_warning ( _("More lines with category %d found"), ccat ); + fprintf (stdout, "%d - -\n", id); + continue; + } + + Vect_read_line ( &Map, Points, Cats, line ); + + G_debug (2, "n_points = %d", Points->n_points ); + if ( Points->n_points < 2 ) { + G_warning ( _("Degenerated line with category %d"), ccat ); + fprintf (stdout, "%d - -\n", id); + continue; + } + + line_len = Vect_line_length (Points); + + if ( from > line_len || to < 0 ) { + G_warning ( _("Segment outside the line (id = %d)"), id ); + fprintf (stdout, "%d - -\n", id); + continue; + } + + if ( from < 0 ) { + G_warning ( _("Start of segment %d < 0"), id ); + from = 0.; + } + if ( to > line_len ) { + G_warning ( _("End of segment %d > line length"), id ); + to = line_len; + } + seg_len = to - from; + G_debug (2, "seg_len = %f", seg_len); + + /* first segment */ + part = Vect_point_on_line ( Points, from, &x, &y, &z, NULL, NULL ); + G_debug (2, "first part = %d", part); + + /* distance from the beginnig of line part */ + dx = x - Points->x[part-1]; + dy = y - Points->y[part-1]; + dz = z - Points->z[part-1]; + dist = hypot (dx, dy); + dist = hypot (dist, dz); + + calc_len = 0; curva = 0; end = 0; + for ( i = part; i < Points->n_points; i++ ) { + G_debug (2, "i = %d", i); + + part_len = part_length (Points, i); + cur = calc_curv (Points, i); + if (abs_flag->answer && (cur < 0) ) cur = -cur; + + rest_len = seg_len - calc_len; + + /* length of used part of part */ + if ( part_len >= (rest_len + dist) ) { /* end of segment reached */ + len = rest_len; + end = 1; + } else { + len = part_len - dist; + } + G_debug (2, "part_len = %f len = %f", part_len, len); + + + /* cumulate curvature */ + curva += cur * len; + G_debug (2, "curva = %f", curva ); + + calc_len += len; + + if ( end ) break; + + dist = 0; /* start of segment from the beginnig of part for next parts */ + } + + curva /= seg_len; + rad = 1 / curva; + + fprintf (stdout, "%d %f %f\n", id, curva, rad); + fflush (stdout); + } + } else if ( OPTION_LINES && cinlines > 0 ) { + Vect_rewind ( &Map ); + while ( (type = Vect_read_next_line (&Map, Points, Cats)) > 0) { + if ( !(type & ctype) ) continue; + + /* TODO: more cats int the same layer */ + cat = 0; + if( Vect_cat_get(Cats, field, &cat) == 0 ) { + continue; + } + + G_debug (2, "n_points = %d", Points->n_points ); + if ( Points->n_points < 2 ) { + G_warning ( _("Degenerated line with category %d"), ccat ); + continue; + } + + for ( part = 1; part < Points->n_points; part++ ) { + G_debug (2, "part = %d", part); + + part_len = part_length (Points, part); + cur = calc_curv (Points, part); + if (abs_flag->answer && (cur < 0) ) cur = -cur; + + rad = 1 / cur; + + fprintf (stdout, "%d %f %f %f\n", cat, part_len, cur, rad); + fflush (stdout); + } + } + } + + Vect_close (&Map); + + exit(EXIT_SUCCESS) ; +} + +/* Calculate curvature of segment */ +double calc_curv (struct line_pnts *Points, int part) +{ + double part_len; + double dx, dy; + double ang, ang1, ang2, part_ang, cur; + + /* segment angle */ + dx = Points->x[part] - Points->x[part-1]; + dy = Points->y[part] - Points->y[part-1]; + part_ang = atan2 ( dy, dx ); + G_debug (2, "part: dx = %f dy = %f part_ang = %f", dx, dy, part_ang); + + /* angle 1 */ + if ( part == 1 ) { /* first segment */ + ang1 = 0; + G_debug (2, "1: first seg ang1 = 0"); + } else { + dx = Points->x[part-1] - Points->x[part-2]; + dy = Points->y[part-1] - Points->y[part-2]; + ang = atan2 ( dy, dx ); + ang1 = part_ang - ang; + G_debug (2, "1: %d->%d: %f, %f -> %f, %f",part-2,part-1, + Points->x[part-2], Points->y[part-2], Points->x[part-1], Points->y[part-1] ); + G_debug (2, "1: dx = %f dy = %.10f ang = %f, ang1 = %f", dx, dy, ang, ang1); + if ( ang1 < -M_PI ) ang1 += 2 * M_PI; + if ( ang1 > M_PI ) ang1 -= 2 * M_PI; + G_debug (2, "1: -> ang1 = %f", ang1); + } + /* angle 2 */ + if ( part == Points->n_points - 1 ) { /* last segment */ + ang2 = 0; + G_debug (2, "2: last seg ang2 = 0"); + } else { + dx = Points->x[part+1] - Points->x[part]; + dy = Points->y[part+1] - Points->y[part]; + ang = atan2 ( dy, dx ); + ang2 = ang - part_ang; + G_debug (2, "2: %d->%d: %f, %f -> %f, %f",part,part+1, + Points->x[part+1], Points->y[part+1], Points->x[part], Points->y[part] ); + G_debug (2, "2: dx = %f dy = %.10f ang = %f ang2 = %f", dx, dy, ang, ang2); + if ( ang2 < -M_PI ) ang2 += 2 * M_PI; + if ( ang2 > M_PI ) ang2 -= 2 * M_PI; + G_debug (2, "2: -> ang2 = %f", ang2); + } + + /* curvature */ + part_len = part_length (Points, part); + cur = 2 * sin ( (ang1 + ang2) / 4 ) / part_len ; + + G_debug (2, "cur = %f R = %f", cur, 1/cur ); + + return cur; +} + +double part_length (struct line_pnts *Points, int part) { + double dx, dy, dz, len; + + /* length of segment */ + dx = Points->x[part] - Points->x[part-1]; + dy = Points->y[part] - Points->y[part-1]; + dz = Points->z[part] - Points->z[part-1]; + len = hypot (dx, dy); + len = hypot (len, dz); + + return len; +} + From cepicky at grass.itc.it Wed Apr 4 16:47:57 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Wed Apr 4 16:47:59 2007 Subject: [grass-addons] r432 - in trunk/grassaddons/gui: . gui_modules Message-ID: <200704041447.l34Elvln014805@grass.itc.it> Author: cepicky Date: 2007-04-04 16:47:57 +0200 (Wed, 04 Apr 2007) New Revision: 432 Modified: trunk/grassaddons/gui/gui_modules/dbm.py trunk/grassaddons/gui/wxgui.py Log: * displaing tables from different mapsets possible * images for sorting added Modified: trunk/grassaddons/gui/gui_modules/dbm.py =================================================================== --- trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-04 07:51:03 UTC (rev 431) +++ trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-04 14:47:57 UTC (rev 432) @@ -25,7 +25,7 @@ import wx import wx.lib.mixins.listctrl as listmix -import sys,os +import sys,os,locale #---------------------------------------------------------------------- class Log: @@ -43,20 +43,25 @@ #---------------------------------------------------------------------- class TestVirtualList(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.ColumnSorterMixin): - def __init__(self, parent,log,tablename,mapset=None,pointdata=None): + def __init__(self, parent,log,vectmap,pointdata=None): wx.ListCtrl.__init__( self, parent, -1, style=wx.LC_REPORT|wx.LC_VIRTUAL|wx.LC_HRULES|wx.LC_VRULES) self.log=log - self.tablename = tablename - self.mapset = mapset + + self.vectmap = vectmap + self.mapname, self.mapset = self.vectmap.split("@") + self.layer,self.tablename, self.column, self.database, self.driver =\ + os.popen("v.db.connect -g map=%s" %\ + (self.vectmap)).readlines()[0].strip().split() + self.icon = '' self.pointsize = '' - self.icon = pointdata[0] - self.pointsize = pointdata[1] + if pointdata: + self.icon = pointdata[0] + self.pointsize = pointdata[1] self.columns = [] - self.columnNumber = 0 self.parent = parent self.qlayer = None @@ -65,13 +70,17 @@ self.attr1.SetBackgroundColour("light blue") self.attr2 = wx.ListItemAttr() self.attr2.SetBackgroundColour("white") + self.il = wx.ImageList(16, 16) + self.sm_up = self.il.Add(wx.ArtProvider_GetBitmap(wx.ART_GO_UP,wx.ART_TOOLBAR,(16,16))) + self.sm_dn = self.il.Add(wx.ArtProvider_GetBitmap(wx.ART_GO_DOWN,wx.ART_TOOLBAR,(16,16))) + self.SetImageList(self.il, wx.IMAGE_LIST_SMALL) #building the columns i = 0 # FIXME: subprocess.Popen should be used # FIXME: Maximal number of columns, when the GUI is still usable - for line in os.popen("db.describe -c table=%s" % - (self.tablename)).readlines()[1:]: + for line in os.popen("db.describe -c table=%s driver=%s database=%s" %\ + (self.tablename, self.driver, self.database)).readlines()[1:]: x,column,type = line.strip().split(":") # FIXME: here will be more types @@ -98,7 +107,8 @@ # FIXME: subprocess.Popen should be used # FIXME: Max. number of rows, while the GUI is still usable i = 0 - for line in os.popen("""db.select -c sql="SELECT * FROM %s" """ % self.tablename): + for line in os.popen("""db.select -c table=%s database=%s driver=%s """ %\ + (self.tablename,self.database,self.driver)): attributes = line.strip().split("|") self.itemDataMap[i] = [] for attribute in attributes: @@ -113,7 +123,7 @@ #mixins listmix.ListCtrlAutoWidthMixin.__init__(self) - listmix.ColumnSorterMixin.__init__(self, 3) + listmix.ColumnSorterMixin.__init__(self, len(self.columns)) #sort by genre (column 2), A->Z ascending order (1) self.SortListItems(0, 1) @@ -129,7 +139,7 @@ if self.qlayer: map.delLayer(item='qlayer') def OnColClick(self,event): - self.columnNumber = event.GetColumn() + self._col = event.GetColumn() event.Skip() def OnItemSelected(self, event): @@ -222,8 +232,14 @@ col = self._col ascending = self._colSortFlag[col] # convert, because the it is allways string - item1 = self.columns[col]["type"](self.itemDataMap[key1][col]) - item2 = self.columns[col]["type"](self.itemDataMap[key2][col]) + try: + item1 = self.columns[col]["type"](self.itemDataMap[key1][col]) + except: + item1 = '' + try: + item2 = self.columns[col]["type"](self.itemDataMap[key2][col]) + except: + item2 = '' #--- Internationalization of string sorting with locale module if type(item1) == type('') or type(item2) == type(''): @@ -245,8 +261,8 @@ # self.columns[self.columnNumber]["type"](b)) # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py - #def GetSortImages(self): - # return (self.sm_dn, self.sm_up) + def GetSortImages(self): + return (self.sm_dn, self.sm_up) #XXX Looks okay to remove this one (was present in the original demo) #def getColumnText(self, index, col): @@ -266,7 +282,7 @@ class AttributeManager(wx.Frame): def __init__(self, parent, id, title, size, style = wx.DEFAULT_FRAME_STYLE, - table=None,mapset=None,pointdata=None): + vectmap=None,pointdata=None): wx.Frame.__init__(self, parent, id, title, size=size, style=style) @@ -277,7 +293,7 @@ # probably self.gismanager = parent - self.win = TestVirtualList(self, log,tablename=table,mapset=mapset,pointdata=pointdata) + self.win = TestVirtualList(self, log,vectmap=vectmap,pointdata=pointdata) self.Show() def main(argv=None): @@ -297,7 +313,7 @@ #wx.InitAllImageHandlers() app = wx.PySimpleApp() - f = AttributeManager(None, -1, "GRASS Attribute Table Manager",wx.Size(500,300),table=argv[1]) + f = AttributeManager(None, -1, "GRASS Attribute Table Manager",wx.Size(500,300),vectmap=argv[1]) app.MainLoop() Modified: trunk/grassaddons/gui/wxgui.py =================================================================== --- trunk/grassaddons/gui/wxgui.py 2007-04-04 07:51:03 UTC (rev 431) +++ trunk/grassaddons/gui/wxgui.py 2007-04-04 14:47:57 UTC (rev 432) @@ -334,11 +334,6 @@ for item in dcmd.split(' '): if 'map=' in item: mapname = item.split('=')[1] - if '@' in mapname: - map = mapname.split('@')[0] - mapset = mapname.split('@')[1] - else: - map = mapname elif 'size=' in item: size = item.split('=')[1] elif 'icon=' in item: @@ -346,12 +341,11 @@ pointdata = (icon,size) - if mapset == grassenv.env["MAPSET"]: - from gui_modules import dbm - self.dbmanager = gui_modules.dbm.AttributeManager(self, - -1,"GRASS Attribute Table Manager: %s" % map, - size=wx.Size(500,300),table=map,mapset=mapset, - pointdata=pointdata) + from gui_modules import dbm + self.dbmanager = gui_modules.dbm.AttributeManager(self, + -1,"GRASS Attribute Table Manager: %s" % map, + size=wx.Size(500,300),vectmap=mapname, + pointdata=pointdata) def newDisplay(self, event=None): From cepicky at grass.itc.it Wed Apr 4 16:53:46 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Wed Apr 4 16:53:48 2007 Subject: [grass-addons] r433 - trunk/grassaddons/gui/gui_modules Message-ID: <200704041453.l34ErkKT015701@grass.itc.it> Author: cepicky Date: 2007-04-04 16:53:46 +0200 (Wed, 04 Apr 2007) New Revision: 433 Modified: trunk/grassaddons/gui/gui_modules/render.py Log: debuging stuff Modified: trunk/grassaddons/gui/gui_modules/render.py =================================================================== --- trunk/grassaddons/gui/gui_modules/render.py 2007-04-04 14:47:57 UTC (rev 432) +++ trunk/grassaddons/gui/gui_modules/render.py 2007-04-04 14:53:46 UTC (rev 433) @@ -594,6 +594,8 @@ # render map layers for layer in self.layers: + if DEBUG: + print "rendering", layer.name # skip if hidden or not active if layer.active == False or layer.hidden == True: continue @@ -611,6 +613,8 @@ maps.append(layer.mapfile) masks.append(layer.maskfile) opacities.append(str(layer.opacity)) + if DEBUG: + print "rendered", layer.name # make arrays to strings mapstr = ",".join(maps) From barton at grass.itc.it Wed Apr 4 16:59:59 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Wed Apr 4 17:00:01 2007 Subject: [grass-addons] r434 - trunk/grassaddons/gui/gui_modules Message-ID: <200704041459.l34Exx6q015737@grass.itc.it> Author: barton Date: 2007-04-04 16:59:50 +0200 (Wed, 04 Apr 2007) New Revision: 434 Modified: trunk/grassaddons/gui/gui_modules/dbm.py Log: Attribute table can now grab map display coordinates when clicked with a mouse. Moving toward implementing v.what to select attribute when vector object is selected. Modified: trunk/grassaddons/gui/gui_modules/dbm.py =================================================================== --- trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-04 14:53:46 UTC (rev 433) +++ trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-04 14:59:50 UTC (rev 434) @@ -27,6 +27,12 @@ import sys,os,locale +try: + from subprocess import * +except: + from compat import subprocess + from compat.subprocess import * + #---------------------------------------------------------------------- class Log: r"""\brief Needed by the wxdemos. @@ -75,6 +81,17 @@ self.sm_dn = self.il.Add(wx.ArtProvider_GetBitmap(wx.ART_GO_DOWN,wx.ART_TOOLBAR,(16,16))) self.SetImageList(self.il, wx.IMAGE_LIST_SMALL) + # show us the result in map display + if self.parent.gismanager: + + self.gism = self.parent.gismanager + curr_pg = self.gism.gm_cb.GetCurrentPage() + disp_idx = self.gism.track.Track().GetDisp_idx(curr_pg) + + self.mapdisp = self.parent.gismanager.mapdisplays[disp_idx] + self.map = self.gism.maptree.Map + + #building the columns i = 0 # FIXME: subprocess.Popen should be used @@ -134,9 +151,11 @@ self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselected) self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick) self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) + self.mapdisp.MapWindow.Bind(wx.EVT_LEFT_DOWN, self.onMapClick) + def OnCloseWindow(self, event): - if self.qlayer: map.delLayer(item='qlayer') + if self.qlayer: self.map.delLayer(item='qlayer') def OnColClick(self,event): self._col = event.GetColumn() @@ -151,23 +170,23 @@ # show us the result in map display if self.parent.gismanager: - gism = self.parent.gismanager - curr_pg = gism.gm_cb.GetCurrentPage() - disp_idx = gism.track.Track().GetDisp_idx(curr_pg) +# gism = self.parent.gismanager +# curr_pg = gism.gm_cb.GetCurrentPage() +# disp_idx = gism.track.Track().GetDisp_idx(curr_pg) - mapdisp = self.parent.gismanager.mapdisplays[disp_idx] - map = gism.maptree.Map - if self.qlayer: map.delLayer(item='qlayer') +# mapdisp = self.parent.gismanager.mapdisplays[disp_idx] +# map = gism.maptree.Map + if self.qlayer: self.map.delLayer(item='qlayer') cat = self.GetItemText(self.currentItem) - cmd = "d.vect map=%s@%s color=yellow fcolor=yellow cats=%s width=3" % (self.tablename, self.mapset, cat) + cmd = "d.vect map=%s@%s color=yellow fcolor=yellow cats=%s width=3" % (self.tablename, self.self.mapset, cat) if self.icon: cmd = cmd +" icon=%s" % (self.icon) if self.pointsize: cmd = cmd + " size=%s" % (self.pointsize) - self.qlayer = map.addLayer(item='qlayer', command=cmd, l_active=True, + self.qlayer = self.map.addLayer(item='qlayer', command=cmd, l_active=True, l_hidden=False, l_opacity=1, l_render=False) - mapdisp.ReDraw(None) + self.mapdisp.ReDraw(None) def OnItemActivated(self, event): self.currentItem = event.m_itemIndex @@ -269,6 +288,64 @@ # item = self.GetItem(index, col) # return item.GetText() + def onMapClick(self, event): + """ + Gets coordinates from mouse clicking on display window + """ + # screen coordinates + posx, posy = event.GetPositionTuple() + + # map coordinates + x, y = self.mapdisp.MapWindow.Pixel2Cell(posx, posy) + print 'coordinates =',x,y + + +# try: +# os.environ["GRASS_MESSAGE_FORMAT"] = "gui" +# cmd = "v.what -a east_north=%d,%d distance=%d map=%@%" % (x,y,100,self.tablename, self.self.mapset) +## self.cmd_output.write(cmd+"\n----------\n") +# p = Popen(cmd +" --verbose", shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) +# +# oline = p.stderr.readline() +# while oline: +# oline = oline.strip() +# oline = p.stderr.readline() +# if oline.find("GRASS_INFO_MESSAGE")>-1: +# self.cmd_output.write(string.split(oline,maxsplit=1)[1]+"\n") +# elif oline.find("GRASS_INFO_WARNING")>-1: +# self.cmd_output.write("WARNING: "+string.split(oline,maxsplit=1)[1]+"\n") +# elif oline.find("GRASS_INFO_ERROR")>-1: +# self.cmd_output.write("ERROR: "+string.split(oline,maxsplit=1)[1]+"\n") +# +# oline = p.stdout.readline() +# while oline: +# oline = oline.strip() +## self.cmd_output.write(oline+"\n") +# print oline+"\n" +# print >> sys.stderr, oline +# oline = p.stdout.readline() +# +# if p.stdout < 0: +# print >> sys.stderr, "Child was terminated by signal", p.stdout +# elif p.stdout > 0: +# print >> sys.stderr, p.stdout +# pass +# except OSError, e: +# print >> sys.stderr, "Execution failed:", e + + + + event.Skip() + + + + + + + + + + #---------------------------------------------------------------------- # The main window #---------------------------------------------------------------------- From cepicky at grass.itc.it Wed Apr 4 17:07:05 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Wed Apr 4 17:07:06 2007 Subject: [grass-addons] r435 - trunk/grassaddons/gui/gui_modules Message-ID: <200704041507.l34F75Ve015822@grass.itc.it> Author: cepicky Date: 2007-04-04 17:07:05 +0200 (Wed, 04 Apr 2007) New Revision: 435 Modified: trunk/grassaddons/gui/gui_modules/dbm.py Log: bug in PNG driver? elusion Modified: trunk/grassaddons/gui/gui_modules/dbm.py =================================================================== --- trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-04 14:59:50 UTC (rev 434) +++ trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-04 15:07:05 UTC (rev 435) @@ -180,7 +180,9 @@ cat = self.GetItemText(self.currentItem) - cmd = "d.vect map=%s@%s color=yellow fcolor=yellow cats=%s width=3" % (self.tablename, self.self.mapset, cat) + # FIXME: width=1, because of maybe bug in PNG driver elusion + # should be width=3 or something like this + cmd = "d.vect map=%s@%s color=yellow fcolor=yellow cats=%s width=1" % (self.tablename, self.self.mapset, cat) if self.icon: cmd = cmd +" icon=%s" % (self.icon) if self.pointsize: cmd = cmd + " size=%s" % (self.pointsize) From cepicky at grass.itc.it Wed Apr 4 17:10:09 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Wed Apr 4 17:10:10 2007 Subject: [grass-addons] r436 - trunk/grassaddons/gui/gui_modules Message-ID: <200704041510.l34FA9cU015868@grass.itc.it> Author: cepicky Date: 2007-04-04 17:10:09 +0200 (Wed, 04 Apr 2007) New Revision: 436 Modified: trunk/grassaddons/gui/gui_modules/dbm.py Log: shorter map name Modified: trunk/grassaddons/gui/gui_modules/dbm.py =================================================================== --- trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-04 15:07:05 UTC (rev 435) +++ trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-04 15:10:09 UTC (rev 436) @@ -182,7 +182,7 @@ # FIXME: width=1, because of maybe bug in PNG driver elusion # should be width=3 or something like this - cmd = "d.vect map=%s@%s color=yellow fcolor=yellow cats=%s width=1" % (self.tablename, self.self.mapset, cat) + cmd = "d.vect map=%s color=yellow fcolor=yellow cats=%s width=1" % (self.vectmap, cat) if self.icon: cmd = cmd +" icon=%s" % (self.icon) if self.pointsize: cmd = cmd + " size=%s" % (self.pointsize) From cepicky at grass.itc.it Wed Apr 4 17:52:43 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Wed Apr 4 17:52:43 2007 Subject: [grass-addons] r437 - trunk/grassaddons/gui/gui_modules Message-ID: <200704041552.l34FqhmM016133@grass.itc.it> Author: cepicky Date: 2007-04-04 17:52:43 +0200 (Wed, 04 Apr 2007) New Revision: 437 Modified: trunk/grassaddons/gui/gui_modules/dbm.py Log: displaing mouse selection in attribute table possible Modified: trunk/grassaddons/gui/gui_modules/dbm.py =================================================================== --- trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-04 15:10:09 UTC (rev 436) +++ trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-04 15:52:43 UTC (rev 437) @@ -151,9 +151,11 @@ self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselected) self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick) self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) - self.mapdisp.MapWindow.Bind(wx.EVT_LEFT_DOWN, self.onMapClick) + if self.parent.gismanager: + self.mapdisp.MapWindow.Bind(wx.EVT_LEFT_DOWN, self.onMapClick) + def OnCloseWindow(self, event): if self.qlayer: self.map.delLayer(item='qlayer') @@ -162,12 +164,14 @@ event.Skip() def OnItemSelected(self, event): + print "Selecting" self.currentItem = event.m_itemIndex self.log.write('OnItemSelected: "%s", "%s"\n' % (self.currentItem, self.GetItemText(self.currentItem))) # show us the result in map display + #print self.par if self.parent.gismanager: # gism = self.parent.gismanager @@ -299,9 +303,25 @@ # map coordinates x, y = self.mapdisp.MapWindow.Pixel2Cell(posx, posy) - print 'coordinates =',x,y + #print 'coordinates =',x,y + category = "" + for line in os.popen("v.what east_north=%f,%f map=%s" %\ + (x,y,self.vectmap)).readlines(): + if "Category:" in line: + category = line.strip().split(" ")[1] + #print category + + for idx in range(self.GetItemCount()): + item = self.GetItem(idx, 0) + if item.GetText() == category: + #print idx + self.Select(idx,True) + else: + self.Select(idx,False) + + # try: # os.environ["GRASS_MESSAGE_FORMAT"] = "gui" # cmd = "v.what -a east_north=%d,%d distance=%d map=%@%" % (x,y,100,self.tablename, self.self.mapset) @@ -341,13 +361,52 @@ +# try: +# os.environ["GRASS_MESSAGE_FORMAT"] = "gui" +# cmd = "v.what -a east_north=%d,%d distance=%d map=%@%" % (x,y,100,self.tablename, self.self.mapset) +## self.cmd_output.write(cmd+"\n----------\n") +# p = Popen(cmd +" --verbose", shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) +# +# oline = p.stderr.readline() +# while oline: +# oline = oline.strip() +# oline = p.stderr.readline() +# if oline.find("GRASS_INFO_MESSAGE")>-1: +# self.cmd_output.write(string.split(oline,maxsplit=1)[1]+"\n") +# elif oline.find("GRASS_INFO_WARNING")>-1: +# self.cmd_output.write("WARNING: "+string.split(oline,maxsplit=1)[1]+"\n") +# elif oline.find("GRASS_INFO_ERROR")>-1: +# self.cmd_output.write("ERROR: "+string.split(oline,maxsplit=1)[1]+"\n") +# +# oline = p.stdout.readline() +# while oline: +# oline = oline.strip() +## self.cmd_output.write(oline+"\n") +# print oline+"\n" +# print >> sys.stderr, oline +# oline = p.stdout.readline() +# +# if p.stdout < 0: +# print >> sys.stderr, "Child was terminated by signal", p.stdout +# elif p.stdout > 0: +# print >> sys.stderr, p.stdout +# pass +# except OSError, e: +# print >> sys.stderr, "Execution failed:", e + event.Skip() + + + + + + #---------------------------------------------------------------------- # The main window #---------------------------------------------------------------------- From chemin at grass.itc.it Wed Apr 4 21:43:22 2007 From: chemin at grass.itc.it (chemin@grass.itc.it) Date: Wed Apr 4 21:43:23 2007 Subject: [grass-addons] r438 - trunk/grassaddons/gipe/script_generator Message-ID: <200704041943.l34JhMho018594@grass.itc.it> Author: chemin Date: 2007-04-04 21:43:18 +0200 (Wed, 04 Apr 2007) New Revision: 438 Modified: trunk/grassaddons/gipe/script_generator/l7_in_read.c Log: Updated script generator to ET Prestley-Taylor Modified: trunk/grassaddons/gipe/script_generator/l7_in_read.c =================================================================== --- trunk/grassaddons/gipe/script_generator/l7_in_read.c 2007-04-04 15:52:43 UTC (rev 437) +++ trunk/grassaddons/gipe/script_generator/l7_in_read.c 2007-04-04 19:43:18 UTC (rev 438) @@ -83,8 +83,9 @@ char sys_9[1000],sys_10[1000],sys_100[1000]; char sys_11[1000],sys_12[1000],sys_13[1000],sys_14[1000]; char sys_15[1000],sys_16[1000],sys_17[1000],sys_18[1000]; + char sys_19[1000],sys_20[1000],sys_21[1000],sys_22[1000]; + char sys_23[1000]; - if(argc < 1){ usage(); } @@ -624,6 +625,17 @@ system(sys_17); sprintf(sys_10,"echo \"r.evapo.TSA RNET=%s.rnetd FV=%s.ndvi TEMPK=%s.61 TEMPKA=%s.tempka ALB=%s.albedo NDVI=%s.ndvi UZ=u2 Z=2.0 Z0=%s.z0h Z0S=z0s W=%s.w TIME=%s.sath SUNH=%s.sunh output=%s.ETA_TSA --overwrite\" >> temp.txt",basedate,basedate,basedate,basedate,basedate,basedate,basedate,basedate,basedate,basedate,basedate); system(sys_10); + sprintf(sys_19,"echo \"r.mapcalc %s.patm=1010.0\" >> temp.txt",basedate); + system(sys_19); + sprintf(sys_20,"echo \"r.emissivity ndvi=%s.ndvi emissivity=%s.e0 --overwrite\" >> temp.txt",basedate,basedate); + system(sys_20); + sprintf(sys_21,"echo \"r.eb.netrad albedo=%s.albedo ndvi=%s.ndvi tempk=%s.61 time=%s.time dtair=%s.delta emissivity=%s.e0 tsw=%s.tsw doy=%s.doy sunzangle=%s.sunza rnet=%s.rnet --overwrite\" >> temp.txt",basedate,basedate,basedate,basedate,basedate,basedate,basedate,basedate,basedate,basedate); + system(sys_21); + sprintf(sys_22,"echo \"r.eb.g0 albedo=%s.albedo ndvi=%s.ndvi tempk=%s.61 rnet=%s.rnet time=%s.time g0=%s.g0 --overwrite\"",basedate,basedate,basedate,basedate,basedate,basedate); + system(sys_22); + sprintf(sys_23,"echo \"r.evapo.PT -z RNET=%s.rnetd G0=%s.g0 TEMPKA=%s.tempka PATM=%s.patm PT=1.26 output=%s.ETA_PT --overwrite\"",basedate,basedate,basedate,basedate,basedate); + system(sys_23); + //clean maps // system("chmod +x temp.txt; cat temp.txt; echo \"Start GRASS Processing\n\" ; ./temp.txt"); From neteler at grass.itc.it Thu Apr 5 08:15:20 2007 From: neteler at grass.itc.it (neteler@grass.itc.it) Date: Thu Apr 5 08:15:22 2007 Subject: [grass-addons] r439 - trunk/grassaddons Message-ID: <200704050615.l356FKM1025736@grass.itc.it> Author: neteler Date: 2007-04-05 08:15:20 +0200 (Thu, 05 Apr 2007) New Revision: 439 Added: trunk/grassaddons/SUBMITTING trunk/grassaddons/SUBMITTING_SCRIPTS trunk/grassaddons/SUBMITTING_TCLTK Log: GRASS submission rules also apply here Added: trunk/grassaddons/SUBMITTING =================================================================== --- trunk/grassaddons/SUBMITTING (rev 0) +++ trunk/grassaddons/SUBMITTING 2007-04-05 06:15:20 UTC (rev 439) @@ -0,0 +1,391 @@ +$Id: SUBMITTING,v 1.22 2006/08/09 09:27:57 markus Exp $ + +NOTE: Please improve this list! + +Dear (new) GRASS Developer, + +When submitting C code to GRASS Addons-SVN repository, please take +care of following rules: + +[ see SUBMITTING_SCRIPTS for shell script hints ] +[ see SUBMITTING_TCLTK for tcl and tk hints ] + + +1. Get and read the GRASS 6 Programmer's Manual here: + http://grass.itc.it/devel/index.php#prog + + or generate it from this source code (the programmer's manual is + integrated in the source code in doxygen style): + make htmldocs + make pdfdocs + + +2. Use the directory structure to place your module appropriately into + the source tree + - libes go into lib/ + - raster goes into raster/ + - vector goes into vector/ + - ... + + Consider to take a look at "GNU Coding Standards" + http://www.gnu.org/prep/standards.html + + In future, there will be a centralized contribution directory. + +3. Add a header section to each file you submit and make sure you include + the copyright. The purpose section is meant to contain a general + overview of the code in the file to assist other programmers that will + need to make changes to your code. + + Example (ficticious header for a file called color.c) : + +/**************************************************************************** + * + * MODULE: d.rast (or new higher level module name (eg GRASS core) + * for 6.1) + * AUTHOR(S): Original author unknown - probably CERL + * John Doe - jdoe@some.where.org + * PURPOSE: To provide storage and manipulation of colors used for + * rendering the raster. The colors are stored in a doubly linked + * list which must be initialized with InitColors() before it can + * be used. Note that most linked list functionality (add, + * remove, get) is supported, but their is no sorting + * functionality. + * COPYRIGHT: (C) 2005 by the GRASS Development Team + * + * This program is free software under the GNU General Public + * License (>=v2). Read the file COPYING that comes with GRASS + * for details. + * + *****************************************************************************/ + + The copyright protects your rights according to GNU General Public + License (www.gnu.org). + + +4. - deleted. + We don't want the $ ID $ in source code any more as it causes problems + for the CVS branches. + + +5. To ensure that the software system continues to work, please include + + #include + + in your files and make use of the various system dependencies + contained therein. As one example of this, see + lib/gmath/fft.c . Please refrain from declaring + system functions within the software; include the proper header files + (conditionally dependent on config.h macros if necessary) instead. + + +6. Order of include headers + + In general, headers should be included in the order: + + 1. Core system headers (stdio.h, ctype.h, ...) + 2. Headers for non-core system components (X11, libraries). + 3. Headers for core systems of the package being compiled (grass/gis.h, grass/glocale.h ...) + 4. Headers for the specific library/program being compiled (geodesic.h, ...) + + Each class of header has an obligation to be compatible with those + above it in the list, but not those below it. + + +7. Always specify the return type for ALL functions including those that + return type "void", and insert return statements for ALL functions. + Also, use ANSI C prototypes to declare your functions. + For module return values, see "Exit status" below. + + Examples: + + void G_something(void); + int G_something_else(int, int); + + void G_something(void) + { + /* Snipped out code */ + + return; + } + + int G_something_else(int x, int y) + { + /* Snipped out code */ + + return(0); + } + + +8. Exit status is defined as EXIT_SUCCESS or EXIT_FAILURE, e.g. + + { + ... + if (G_parser (argc, argv)) + exit (EXIT_FAILURE); + + ... + exit (EXIT_SUCCESS) + } + + +9. Use fprintf instead of printf. But: + + For errors and warnings please use the G_fatal_error() and + G_warning() functions. General messages for the user should use + G_message() while debug messages should use G_debug() whenever + possible. + + G_message() output is not expected to be sent to pipe or file. + + Always use the gettext macros with _("") for user messages, + example: + G_fatal_error ( _("Vector file [%s] not available"), name); + + + Pipe/file data output: + For data output redirected to pipe or file, please use fprintf() and + specify the stdout stream as follows: + + fprintf(stdout, ...); + fflush(stdout); + + fflush(stdout) always required when using fprintf(stdout, ...). + + +10. Use the GRASS library function G_asprintf() instead of the + standard C functions asprintf(), vsnprintf() and snprintf(). These + functions are not portable or have other issues. Example: + + char *msg; + + G_asprintf(&msg, "%s", parameters); + do_something_with_msg(); + G_free(msg); + + Note that you should free memory when G_asprintf() is used. + + +11. Use the following GRASS library functions instead of the standard C + functions. The reason for this is that the following functions ensure + good programming practice (eg always checking if memory was allocated) + and/or improves portability. PLEASE refer to the programmers manual + for the proper use (eg determining if any casts are needed for arguments + or return values) of these library functions. They may perform a task + slightly different from their corresponding C library function, and thus, + their use may not be the same. + + G_malloc() instead of malloc() + G_calloc() instead of calloc() + G_realloc() instead of realloc() + G_free() instead of free() + G_getenv() instead of getenv() + G_setenv() instead of setenv() + G_unsetenv() instead of unsetenv() + G_sleep() instead of sleep() + + Could somebody please add others (please verify that they are + useful and safe first) + +12. Use function names which fulfil the official GNU naming convention. + http://www.gnu.org/prep/standards/html_node/Names.html#Names + + Instead of naming a function like: MyNewFunction() use underscores + for seperation and lower case letters: my_new_function(). + +13. Don't use the C++ comment style! This confuses several compilers. + Use instead: + /* C-comments */ + + If you want to comment code portions, use + #ifdef notdef + portion_to_be_commented; + #endif + This is safe comparing to nested /* comments */ + + Functions in the library must be documented in doxygen style to + get them into the programmer's manual (generate with + make pdfdocs or + make htmldocs + ). See lib/gis/*.c for examples. + + +14. PLEASE take the time to add comments throughout your code explaining what + the code is doing. It will save a HUGE amount of time and frustration for + other programmers that may have to change your code in the future. + + +15. To promote a consistent coding style, please use the "indent" program + on all new C modules using the following switches: + + $ indent -nbad -bap -bbb -nbbo -nbc -br -bli1 -bls -cbi0 -ncdb -nce \ + -ci4 -cli0 -ncs -d0 -di0 -fc1 -nfca -hnl -i4 -ip4 -l80 -lc80 -lp \ + -npcs -pi4 -nprs -npsl -sbi0 -sc -nsob -ss -ts8 main.c + + Existing code should not be re-indented except in extreme cases, as this + will make "diff" comparisons with older versions impossible. If indent is + needed, do not check in any changes other than the indentation in the same + commit! Do add the indent switches and any indent warning messages to the + CVS log. Any change or fix mixed in with an indent is very hard to track + making it hard for others to follow the change or fix any new bugs. + + +16. Platform dependent code: + Do not remove #ifdef __CYGWIN__ and/or #ifndef __CYGWIN__ lines and + their encapsulated lines from source code (one example was that someone + removed drand48 definition.) + +17. Suggested compiler flags: + We suggest to use very strict compiler flags to capture errors + at the very beginning. Here our list of flags, please use them + to configure you development version of GRASS: + + GNU/Linux: + + MYCFLAGS="-g -Wall -Werror-implicit-function-declaration -fno-common" + MYCXXFLAGS="-g -Wall" + + CFLAGS="$MYCFLAGS" CXXFLAGS="$MYCXXFLAGS" ./configure ... + + MacOSX: [to be suggested] + + MS-Windows: [to be suggested] + +18. Make sure a new line is at the end of each file. + + +19. When writing Makefiles, use the current standard. + + If you have to use commands, please check for: + + avoid | use instead + ------------------+--------------- + make target | $(MAKE) target + mkdir target | $(MKDIR) target + cp (executable) | $(INSTALL) -m 755 file target + cp (normal file) | $(INSTALL) -m 644 file target + ar | $(AR) + + rm: be VERY careful with recursive remove. + + Examples: see below examples or others + raster/r.info/Makefile + vector/v.digit/Makefile + + If you are unsure, please ask on the GRASS Developers list. + +20. Have a look at ./INSTALL + + +21. Have a function included in your module which writes to the history + file of the map (e.g. command line, parameters etc.). See eg + raster/r.patch/main.c + + (the same applies to vector and g3d modules!) + + +22. Standard parser options: use G_define_standard_option() whenever possible + to define standard module command line options. This will save you time, + create fewer bugs, and make things easier on the translators. + See lib/gis/parser.c for details of the function definition. + + +23. Module manual page: + Place the documentation in HTML format into 'description.html'. + The easiest way to do this is to study an existing HTML page + (to get the page style, e.g. vector/v.to.db/description.html). + With a few exceptions header and footer are NOT allowed. + You can add figures (PNG format), the figure name prefix should be the + module name. See raster/r.terraflow/description.html for an example. + + Note that the parameter information is auto-generated upon + compilation. This is done by running module in a virtual session + after compilation (see the output of 'make'). To subsequently + verify the final HTML page, check the resulting HTML pages which + will be stored with the name of the module. + + Examples (please add some) should be coded like this: + +

+    v.to.db map=soils type=area option=area col=area_size unit=h
+    
+ + The online WWW man pages will be updated every Saturday by CVS. + + +24. Add/update, if required the related GUI menus: + gui/tcltk/menus/menu.tcl + + +25. For consistency, use README rather than README.txt for any README files. + + +26. GRASS/Environment variables: + If you add a new variable, please follow the naming convention. + All variables are described in + lib/init/variables.html + + +27. Be sure to develop on top of the LATEST GRASS code (which is in CVS). + You can re-check before submission with 'cvs diff': + + Be sure to create unified ("diff -u") format. "Plain" diffs (the default + format) are risky, because they will apply without warning to code which + has been substantially changed; they are also harder to read than unified. + + Such diffs should be made from the top-level directory, e.g. + "cvs diff -u display/d.vect/main.c"; that way, the diff will + include the pathname rather than just "main.c". + +28. Try to use module names which describe shortly the intended purpose of the module. + + The first letters for module name should be: + d. - display commands + db. - database commands + g. - general commands + i. - imagery commands + p. - paint commands + ps. - postscript commands + r. - raster commands + r3. - raster3D commands + v. - vector commands + + Some additional naming conventions + * export modules: (type).out.(format) eg: r.out.arc, v.out.ascii + * import module: (type).in.(format) eg: r.in.arc, v.in.ascii + * conversion modules: (type).to.(type) eg: r.to.vect, v.to.rast, r3.to.rast + + Avoid module names with more than two dots in the name. + Example: + instead of r.to.rast3.elev use r.to.rast3elev + +28. Use the grass test suite to test your modules. + + http://www-pool.math.tu-berlin.de/~soeren/grass/GRASS_TestSuite + + You can easily write specific tests for your modules. + + If your module is part of grass and you created some standard test cases, + please contact the developers to add your tests to the default test suite. + This will automatize complex test scenarios and assure to find bugs much + faster, if changes were made to your modules or to the grass library. + + Consider to subscribe to the GRASS Quality Assessment System to + get immediate notification about the code quality: + + http://grass.itc.it/mailman/listinfo/grass-qa + +29. Tell the other developers about the new code using the following e-mail: + grass-dev@grass.itc.it + + To subscribe to this mailing list, see + http://grass.itc.it/devel/index.php + + +30. In case of questions feel free to contact the developers at the above + mailing list. + http://grass.itc.it/devel/index.php#submission + +... +[please add further hints if required] + Added: trunk/grassaddons/SUBMITTING_SCRIPTS =================================================================== --- trunk/grassaddons/SUBMITTING_SCRIPTS (rev 0) +++ trunk/grassaddons/SUBMITTING_SCRIPTS 2007-04-05 06:15:20 UTC (rev 439) @@ -0,0 +1,188 @@ +$Id: SUBMITTING_SCRIPTS,v 1.9 2007/03/23 13:10:45 jachym Exp $ + +NOTE: Please improve this list! + +Dear (new) GRASS Developer, + +When submitting SHELL SCRIPTS to GRASS Addons-SVN repositiory, +please take care of following rules: + +[ see SUBMITTING for C code hints ] +[ see SUBMITTING_TCLTK for tcl and tk hints ] + +0. Instructions for the GRASS script parser can be found in the g.parser + module's help page. + http://grass.ibiblio.org/grass63/manuals/html63_user/g.parser.html + +1. Use the directory structure to place your script appropriately into + the source tree + - scripts go into scripts/ + + Consider to take a look at [please suggest Shell tutorial] + +2. Add a header section to the script you submit and make sure you include + the copyright. The purpose section is meant to contain a general + overview of the code in the file to assist other programmers that will + need to make changes to your code. + + Example (ficticious header for a script called r.myscript) : + +#!/bin/sh + +############################################################################ +# +# MODULE: r.myscript +# AUTHOR(S): Me +# PURPOSE: Calculates univariate statistics from a GRASS raster map +# COPYRIGHT: (C) 2005 by the GRASS Development Team +# +# This program is free software under the GNU General Public +# License (>=v2). Read the file COPYING that comes with GRASS +# for details. +# +############################################################################# + + The copyright protects your rights according to GNU General Public + License (www.gnu.org). + +3. - deleted. + We don't want the $ ID $ in scripts any more as it + causes problems for the CVS branches. + +4. As a general principle, shell variables should almost always be quoted. + Use only secure temp files, see g.tempfile and scripts/* for examples. + +5. [This rule is currently under review] If you search for a command in $PATH, do NOT + use the "which" command or the "type -p" command. Both commands are not + supported on all platforms, thus causing problems for some people. As an + alternative, please use code similar to the following shell script snippet + which will perform the same function. In this case, the path of the grass60 + command is saved if grass60 is found in $PATH. This won't recognize aliased + command name. + + # Search for grass5 command in user's path + for i in `echo $PATH | sed 's/^:/.:/ + s/::/:.:/g + s/:$/:./ + s/:/ /g'` + do + if [ -f $i/grass5 ] ; then + + # Save the path of the grass60 command + GRASS_PATH=$i/grass60 + # Use the first one in user's path + break + fi + done + + + If you must use "which", use as follows: + + # check if we have awk + if [ ! -x "`which awk`" ] ; then + g.message -e "awk required, please install awk or gawk first" + exit 1 + fi + + +6. Add a test to check if the user is in GRASS before starting main part + of script. Result of running the script is unpredicable otherwise. + + if [ -z "$GISBASE" ] ; then + echo "You must be in GRASS GIS to run this program." 1>&2 + exit 1 + fi + + This is the only case, where g.message module (see below) can not be used. + In all other cases, you should prefer it's usage over the 'echo' command. + +7. Create and use secure temporary files and directories. Use the g.tempfile + module to do this. e.g. + + # setup temporary file + TMP="`g.tempfile pid=$$`" + if [ $? -ne 0 ] || [ -z "$TMP" ] ; then + g.message -e "unable to create temporary files" + exit 1 + fi + + For temportary directories remove the newly created file and mkdir using + the same name. Beware of commands like "rm -f ${TMP}*" as this becomes + "rm -f *" if $TMP is unset (hence the test above). + +8. Testing the existence of variables. For portability, use + if [ -z "$VARIABLE" ] ; then + instead of + if [ "$VARIABLE" == "" ] ; then + + and + + if [ -n "$VARIABLE" ] ; then + instead of + if [ "$VARIABLE" != "" ] ; then + + +9. Internationalization proofing Awk: In some areas (e.g. some European + countries) the decimal place is held with a comma instead of a dot. + When scanning variables awk doesn't understand this, so scripts need to + temporarily discard locale settings before calling awk. + + # set environment so that awk works properly in all languages + unset LC_ALL + export LC_NUMERIC=C + + awk '{print $1}' + + +10. Use g.findfile when there is a need to test if a map exists. + + # test for input raster map + g.findfile element=cell file="$INPUT" > /dev/null + if [ $? -eq 0 ] ; then + g.message -e "Input map not found" + exit 1 + fi + +11. For any informational output, use the g.message modul. This module should + be also used for error messages and warnings. You can also use it for + debugging purposes. + + g.message "Done." + g.message -w "No input values found, using default values" + g.message -e "No map found, exiting." + g.message -d "Our calculated value is: $value" + + Try to omit any usage of the 'echo' command for informational output. + +12. For consistency, use README rather than README.txt for any README files. + +13. Be sure to develop on top of the LATEST GRASS code (which is in CVS). + You can re-check before submission with 'cvs diff': + + Be sure to create unified ("diff -u") format. "Plain" + diffs (the default format) are risky, because they will apply without + warning to code which has been substantially changed; they are also + harder to read than unified. + + Such diffs should be made from the top-level directory, e.g. + "cvs diff -u display/d.vect/main.c"; that way, the diff will + include the pathname rather than just "main.c". + +14. Tell the other developers about the new code using the following e-mail: + grass-dev@grass.itc.it + + To subscribe to this mailing list, see + http://grass.itc.it/devel/index.php + +15. In case of questions feel free to contact the developers at the above + mailing list. + http://grass.itc.it/devel/index.php#submission + + +16. For portability, scripts must work on any POSIX compliant shell, and + therefore may not include any Bashisms. Test with ash for errors: + + ash -n scriptname + +... +[please add further hints if required] Added: trunk/grassaddons/SUBMITTING_TCLTK =================================================================== --- trunk/grassaddons/SUBMITTING_TCLTK (rev 0) +++ trunk/grassaddons/SUBMITTING_TCLTK 2007-04-05 06:15:20 UTC (rev 439) @@ -0,0 +1,245 @@ +NOTE: Please improve this list! + +Dear (new) GRASS Developer, + +When submitting TCL and TK SCRIPTS and C code to GRASS Addons-SVN repository, +please take care of following rules: + +[ see SUBMITTING for C code hints ] +[ see SUBMITTING_SCRIPTS for shell script hints ] + +1. Use the directory structure to place your program appropriately into + the source tree + - general grass tcl/tk libraries and reusable code go into lib/gtcltk + - user interfaces go in gui/tcltk + - scripts go in scripts/ + Programs here must have a proper Makefile and description.html + +2. Add a header section to the script you submit and make sure you include + the copyright. The purpose section is meant to contain a general + overview of the code in the file to assist other programmers that will + need to make changes to your code. + + Example (fictitious header for a script called r.myscript) : + +############################################################################ +# +# MODULE: r.myscript +# AUTHOR(S): Me +# PURPOSE: Calculates univariate statistics from a GRASS raster map +# COPYRIGHT: (C) 2005 by the GRASS Development Team +# +# This program is free software under the GNU General Public +# License (>=v2). Read the file COPYING that comes with GRASS +# for details. +# +############################################################################# + + The copyright protects your rights according to GNU General Public + License (www.gnu.org). + +3. PLEASE take the time to add comments throughout your code explaining what + the code is doing. It will save a HUGE amount of time and frustration for + other programmers that need to change or understand your code in the future. + Many of the programmers working on grass are not heavily invested in tcl + and tk, so extra documentation and explanation are greatly appreciated. + +4. Test your program with both tcl/tk 8.3 and tcl/tk 8.4. + +5. Always use the gettext macros with [G_msg "..."] for user messages. + The string must be quoted using quotation marks, not braces, for + xgettext to find it. The string cannot include variable ($) or + command ([...]) substitutions. If you need substitutions use + [format ...]. + + Examples: + button .ok -text [G_msg "Ok"] + + set statusbartext [format [G_msg "Monitor %d running"] $monitor_number]] + + Use positional parameters if substitutions might be rearranged in another language: + + format [G_msg "We produced %1\$d units in location %2\$s"] $num $city + format [G_msg "In location %2\$s we produced %1\$d units"] $num $city + +6. Use "GRASS_TCLSH" and "GRASS_WISH" environment variables instead of + "tclsh" and "wish" at the start of Tcl/Tk scripts. This allows users to + override the default names, so that developers don't need worry about the + shell names. + + Tcl script: + + #!/bin/sh + # the next line restarts using tclsh. Note the backslash: \ + exec $GRASS_TCLSH "$0" "$@" + + + Tk script: + + #!/bin/sh + # the next line restarts using wish. Note the backslash: \ + exec $GRASS_WISH "$0" "$@" + +7. Do not source files that have already been sourced. + + gui.tcl sources: + options.tcl + select.tcl + gronsole.tcl + + If your code requires something to be sourced before it note so + in a comment at the top of the file. + +8. Set tabstops in your editor to 8 spaces. + When modifying files use the indentation style that is already present. + Please use consistent indentation style in your new code. Whether you use + tabs or spaces to indent please be consistent. Where tabs and spaces are + mixed please remember that a tab is 8 spaces. + +9. Use the tk options database to control the appearance of your user interface. + In general do not set options on tk widgets unless that option is truly + specific to that widget. It makes them harder to customize. + + Example: Don't set options like -foreground or -background or -font + when creating widgets, unless there's a really _really_ specific reason to + have it that color (like it's demonstrating that color). + + If you want something like a label to look different than everything else + of that class (other labels) give it a distinctive name, like + .moduletitlelabel . If you have a bunch of them give them all the same + distinctive name. This allows them to have their options controlled by the + options database. + + You can put your options at the start of your script (before creating any + widgets) like this: + option add *modultitlelabel.background red + More examples are in lib/gtcltk/options.tcl + + Many common options, like font, background and foreground colors, + highlighting, scrollbar colors, and help bubble appearance are controlled + by options.tcl. You can include it at the start of your script with: + source $env(GISBASE)/etc/gtcltk/options.tcl + +10. Avoid using global variables. Thay are a frequent source of bugs, make code + harder to understand, and make your program difficult to reuse. Additionally, + putting code into procs usually makes it run faster (it gets compiled). + +11. For consistency, use README rather than README.txt for any README files. + +12. Use of GRASS commands in shell wrapper. + + Any GRASS program run in an xterm (those which do interactive query) needs + to use grass-run.sh, e.g.: + + exec -- $env(GISBASE)/etc/grass-xterm-wrapper -e $env(GISBASE)/etc/grass-run.sh g.proj ... + + You should probably also use "-T g.proj -n g.proj" to set the title + back (otherwise it will be "grass-run.sh", which isn't particularly + informative). + + The xterm will close as soon as the command completes (whether it + succeeds or fails). You can use the -hold switch to retain the xterm + window after the command completes, but you should only do that for + debugging; having to manually close the xterm window each time would + be annoying in normal use. Alternatively, redirect stdout/stderr to a + file, to catch any error messages. + +13. Be sure to develop on top of the LATEST GRASS code (which is in CVS). + You can re-check before submission with 'cvs diff': + + Be sure to create unified ("diff -u") format. "Plain" + diffs (the default format) are risky, because they will apply without + warning to code which has been substantially changed; they are also + harder to read than unified. + + Such diffs should be made from the top-level directory, e.g. + "cvs diff -u display/d.vect/main.c"; that way, the diff will + include the pathname rather than just "main.c". + +14. Tell the other developers about the new code using the following e-mail: + grass-dev@grass.itc.it + + To subscribe to this mailing list, see + http://grass.itc.it/devel/index.php + +15. In case of questions feel free to contact the developers at the above + mailing list. + http://grass.itc.it/devel/index.php#submission + +16. Try to evaluate things only once. Tcl compiles the program to bytecode which + can be interpreted fairly quickly. If there are strings that must still be + evaluated tcl must parse and either compile or interpret them + each time they are encountered. In general this means put braces around + expressions and especially regular expressions (Tcl also compiles regular + expressions). Multiple evaluation can also lead to bugs. + + Expressions via expr command: + Slow: + set y [expr $a * $x + $b] + Fast: + set y [expr {$a * $x + $b}] + + Expressions in conditions: + Slow: + if [...] {... + Fast: + if {[...]} {... + + Regular expressions: + Very slow: + regex "x(\[0-9\]+).*not" $string trash number + Fast: + regex {x([0-9]+).*not} $string trash number + + If you really want speed: + If a regular expression needs to be constructed from variables but used + multiple times store it in a variable that will not be destroyed or + changed between reuse. Tcl stores the compiled regex with the variable. + +17. You might want to decompose lists in a somewhat easy way: + + Difficult and slow: + # Make x1 y1 x2 y2 the first four things in the list + set list [commandMakesList] + set x1 [lindex $list 0] + set y1 [lindex $list 1] + set x2 [lindex $list 2] + set y2 [lindex $list 3] + + Easier and faster: + # Make x1 y1 x2 y2 the first four things in the list + foreach {x1 y1 x2 y2} [commandMakesList] {break} + + Be sure to include a comment as to what you are doing. + +18. Use the Tcl list functions (list, lappend etc) for manipulating lists. + + For example, use: + + set vals [list $foo $bar] + + rather than: + + set vals "$foo $bar" + + The former will always create a list with two elements, adding braces + if necessary, while the latter will split $foo and $bar into multiple + elements if they contain spaces. Additionally the first is faster + because tcl is not internally converting between strings and lists. + + A related issue is to remember that command lines (as used by exec and + open "|...") are lists. exec behaves like execl(), spawnl() etc, and + not like system(). + + Overlooking either of these points is likely to result in code which + fails when a command argument contains a space. + +19. Tcl C library: + Memory allocated with Tcl_Alloc (such as the result of Tcl_Merge) + must be freed with Tcl_Free. This means that the ->freeProc of + an interpreter when returning a string using Tcl_Merge should be + TCL_DYNAMIC. Incorrectly freeing memory with glibc free will + cause segfaults in Tcl 8.4 and later. + +... +[please add further hints if required] From cepicky at grass.itc.it Thu Apr 5 08:42:36 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Thu Apr 5 08:42:37 2007 Subject: [grass-addons] r440 - trunk/grassaddons/gui/gui_modules Message-ID: <200704050642.l356gaCl025815@grass.itc.it> Author: cepicky Date: 2007-04-05 08:42:36 +0200 (Thu, 05 Apr 2007) New Revision: 440 Modified: trunk/grassaddons/gui/gui_modules/dbm.py Log: multiple selection event now works, but the whole table is slower (wx.VIRTUAL removed) Modified: trunk/grassaddons/gui/gui_modules/dbm.py =================================================================== --- trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-05 06:15:20 UTC (rev 439) +++ trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-05 06:42:36 UTC (rev 440) @@ -50,7 +50,7 @@ class TestVirtualList(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.ColumnSorterMixin): def __init__(self, parent,log,vectmap,pointdata=None): - wx.ListCtrl.__init__( self, parent, -1, style=wx.LC_REPORT|wx.LC_VIRTUAL|wx.LC_HRULES|wx.LC_VRULES) + wx.ListCtrl.__init__( self, parent, -1, style=wx.LC_REPORT|wx.LC_HRULES|wx.LC_VRULES) #wx.VIRTUAL self.log=log @@ -124,19 +124,35 @@ # FIXME: subprocess.Popen should be used # FIXME: Max. number of rows, while the GUI is still usable i = 0 + # read data for line in os.popen("""db.select -c table=%s database=%s driver=%s """ %\ (self.tablename,self.database,self.driver)): attributes = line.strip().split("|") self.itemDataMap[i] = [] - for attribute in attributes: - self.itemDataMap[i].append(attribute) - self.itemIndexMap.append(i) + + # convert to appropriate type + for j in range(len(attributes)): + try: + attributes[j] = self.columns[j]["type"](attributes[j]) + except: + pass + + # insert to table + index = self.InsertStringItem(sys.maxint, str(attributes[0])) + self.itemDataMap[i].append(attributes[0]) + for j in range(len(attributes[1:])): + self.SetStringItem(index, j+1, str(attributes[j+1])) + self.itemDataMap[i].append(attributes[j+1]) + + self.SetItemData(index, i) + self.itemIndexMap.append(i) + i += 1 if i >= 32000: self.log.write("Can display only 32000 lines") break - self.SetItemCount(len(self.itemDataMap)) + #self.SetItemCount(len(self.itemDataMap)) #mixins listmix.ListCtrlAutoWidthMixin.__init__(self) @@ -146,12 +162,20 @@ self.SortListItems(0, 1) #events - self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected) - self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated) - self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselected) - self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick) - self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) + self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected, self) + self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselected, self) + self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated, self) + #self.Bind(wx.EVT_LIST_DELETE_ITEM, self.OnItemDelete, self.list) + self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick, self) + #self.Bind(wx.EVT_LIST_COL_RIGHT_CLICK, self.OnColRightClick, self.list) + #self.Bind(wx.EVT_LIST_COL_BEGIN_DRAG, self.OnColBeginDrag, self.list) + #self.Bind(wx.EVT_LIST_COL_DRAGGING, self.OnColDragging, self.list) + #self.Bind(wx.EVT_LIST_COL_END_DRAG, self.OnColEndDrag, self.list) + #self.Bind(wx.EVT_LIST_BEGIN_LABEL_EDIT, self.OnBeginEdit, self.list) + #self.list.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick) + #self.list.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown) + if self.parent.gismanager: self.mapdisp.MapWindow.Bind(wx.EVT_LEFT_DOWN, self.onMapClick) @@ -193,28 +217,31 @@ self.qlayer = self.map.addLayer(item='qlayer', command=cmd, l_active=True, l_hidden=False, l_opacity=1, l_render=False) self.mapdisp.ReDraw(None) + event.Skip() def OnItemActivated(self, event): self.currentItem = event.m_itemIndex self.log.write("OnItemActivated: %s\nTopItem: %s\n" % (self.GetItemText(self.currentItem), self.GetTopItem())) + event.Skip() def getColumnText(self, index, col): item = self.GetItem(index, col) return item.GetText() - def OnItemDeselected(self, evt): + def OnItemDeselected(self, event): self.log.write("OnItemDeselected: %s" % evt.m_itemIndex) + event.Skip() #--------------------------------------------------- # These methods are callbacks for implementing the # "virtualness" of the list... - def OnGetItemText(self, item, col): - index=self.itemIndexMap[item] - s = self.itemDataMap[index][col] - return s + # def OnGetItemText(self, item, col): + # index=self.itemIndexMap[item] + # s = self.itemDataMap[index][col] + # return s # def OnGetItemImage(self, item): # index=self.itemIndexMap[item] @@ -235,55 +262,55 @@ # the ColumnSorterMixin.__ColumnSorter() method already handles the ascending/descending, # and it knows to sort on another column if the chosen columns have the same value. - def SortItems(self,sorter=cmp): - items = list(self.itemDataMap.keys()) - # for i in range(len(items)): - # items[i] = self.columns[self.columnNumber]["type"](items[i]) - items.sort(self.Sorter) - #items.sort(sorter) - # for i in range(len(items)): - # items[i] = str(items[i]) - self.itemIndexMap = items + # def SortItems(self,sorter=cmp): + # items = list(self.itemDataMap.keys()) + # # for i in range(len(items)): + # # items[i] = self.columns[self.columnNumber]["type"](items[i]) + # items.sort(self.Sorter) + # #items.sort(sorter) + # # for i in range(len(items)): + # # items[i] = str(items[i]) + # self.itemIndexMap = items - # redraw the list - self.Refresh() + # # redraw the list + # self.Refresh() # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py def GetListCtrl(self): return self - # stolen from python2.4/site-packages/wx-2.8-gtk2-unicode/wx/lib/mixins/listctrl.py - def Sorter(self, key1,key2): - col = self._col - ascending = self._colSortFlag[col] - # convert, because the it is allways string - try: - item1 = self.columns[col]["type"](self.itemDataMap[key1][col]) - except: - item1 = '' - try: - item2 = self.columns[col]["type"](self.itemDataMap[key2][col]) - except: - item2 = '' + # # stolen from python2.4/site-packages/wx-2.8-gtk2-unicode/wx/lib/mixins/listctrl.py + # def Sorter(self, key1,key2): + # col = self._col + # ascending = self._colSortFlag[col] + # # convert, because the it is allways string + # try: + # item1 = self.columns[col]["type"](self.itemDataMap[key1][col]) + # except: + # item1 = '' + # try: + # item2 = self.columns[col]["type"](self.itemDataMap[key2][col]) + # except: + # item2 = '' - #--- Internationalization of string sorting with locale module - if type(item1) == type('') or type(item2) == type(''): - cmpVal = locale.strcoll(str(item1), str(item2)) - else: - cmpVal = cmp(item1, item2) - #--- + # #--- Internationalization of string sorting with locale module + # if type(item1) == type('') or type(item2) == type(''): + # cmpVal = locale.strcoll(str(item1), str(item2)) + # else: + # cmpVal = cmp(item1, item2) + # #--- - # If the items are equal then pick something else to make the sort v ->alue unique - if cmpVal == 0: - cmpVal = apply(cmp, self.GetSecondarySortValues(col, key1, key2)) + # # If the items are equal then pick something else to make the sort v ->alue unique + # if cmpVal == 0: + # cmpVal = apply(cmp, self.GetSecondarySortValues(col, key1, key2)) - if ascending: - return cmpVal - else: - return -cmpVal + # if ascending: + # return cmpVal + # else: + # return -cmpVal - #return cmp(self.columns[self.columnNumber]["type"](a), - # self.columns[self.columnNumber]["type"](b)) + #return cmp(self.columns[self.columnNumber]["type"](a), + # self.columns[self.columnNumber]["type"](b)) # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py def GetSortImages(self): From cepicky at grass.itc.it Thu Apr 5 09:12:43 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Thu Apr 5 09:12:44 2007 Subject: [grass-addons] r441 - trunk/grassaddons/gui/gui_modules Message-ID: <200704050712.l357ChfG025885@grass.itc.it> Author: cepicky Date: 2007-04-05 09:12:43 +0200 (Thu, 05 Apr 2007) New Revision: 441 Modified: trunk/grassaddons/gui/gui_modules/dbm.py Log: displaing multiple selection now possible and usable Modified: trunk/grassaddons/gui/gui_modules/dbm.py =================================================================== --- trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-05 06:42:36 UTC (rev 440) +++ trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-05 07:12:43 UTC (rev 441) @@ -25,7 +25,7 @@ import wx import wx.lib.mixins.listctrl as listmix -import sys,os,locale +import sys,os,locale,string try: from subprocess import * @@ -33,7 +33,6 @@ from compat import subprocess from compat.subprocess import * -#---------------------------------------------------------------------- class Log: r"""\brief Needed by the wxdemos. The log output is redirected to the status bar of the containing frame. @@ -68,6 +67,8 @@ self.pointsize = pointdata[1] self.columns = [] + self.selectedCats = [] + self.lastTurnSelectedCats = [] self.parent = parent self.qlayer = None @@ -179,6 +180,9 @@ if self.parent.gismanager: self.mapdisp.MapWindow.Bind(wx.EVT_LEFT_DOWN, self.onMapClick) + self.timer = wx.PyTimer(self.RedrawMap) + # check each 0.1s + self.timer.Start(100) def OnCloseWindow(self, event): if self.qlayer: self.map.delLayer(item='qlayer') @@ -188,36 +192,36 @@ event.Skip() def OnItemSelected(self, event): - print "Selecting" self.currentItem = event.m_itemIndex self.log.write('OnItemSelected: "%s", "%s"\n' % (self.currentItem, self.GetItemText(self.currentItem))) + self.selectedCats.append(self.GetItemText(self.currentItem)) - # show us the result in map display - #print self.par - if self.parent.gismanager: + event.Skip() + def RedrawMap(self): + # gism = self.parent.gismanager # curr_pg = gism.gm_cb.GetCurrentPage() # disp_idx = gism.track.Track().GetDisp_idx(curr_pg) # mapdisp = self.parent.gismanager.mapdisplays[disp_idx] # map = gism.maptree.Map + + if self.lastTurnSelectedCats[:] != self.selectedCats[:]: if self.qlayer: self.map.delLayer(item='qlayer') - cat = self.GetItemText(self.currentItem) - # FIXME: width=1, because of maybe bug in PNG driver elusion # should be width=3 or something like this - cmd = "d.vect map=%s color=yellow fcolor=yellow cats=%s width=1" % (self.vectmap, cat) + cmd = "d.vect map=%s color=yellow fcolor=yellow cats=%s width=1" % (self.vectmap, string.join(self.selectedCats,sep=",")) if self.icon: cmd = cmd +" icon=%s" % (self.icon) if self.pointsize: cmd = cmd + " size=%s" % (self.pointsize) self.qlayer = self.map.addLayer(item='qlayer', command=cmd, l_active=True, - l_hidden=False, l_opacity=1, l_render=False) + l_hidden=False, l_opacity=1, l_render=False) self.mapdisp.ReDraw(None) - event.Skip() + self.lastTurnSelectedCats = self.selectedCats[:] def OnItemActivated(self, event): self.currentItem = event.m_itemIndex @@ -230,7 +234,8 @@ return item.GetText() def OnItemDeselected(self, event): - self.log.write("OnItemDeselected: %s" % evt.m_itemIndex) + self.log.write("OnItemDeselected: %s" % event.m_itemIndex) + self.selectedCats.remove(self.GetItemText(event.m_itemIndex)) event.Skip() From cepicky at grass.itc.it Thu Apr 5 10:06:47 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Thu Apr 5 10:06:48 2007 Subject: [grass-addons] r442 - trunk/grassaddons/gui/gui_modules Message-ID: <200704050806.l3586lIG026001@grass.itc.it> Author: cepicky Date: 2007-04-05 10:06:47 +0200 (Thu, 05 Apr 2007) New Revision: 442 Modified: trunk/grassaddons/gui/gui_modules/dbm.py Log: selection and map display usable now Modified: trunk/grassaddons/gui/gui_modules/dbm.py =================================================================== --- trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-05 07:12:43 UTC (rev 441) +++ trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-05 08:06:47 UTC (rev 442) @@ -68,7 +68,7 @@ self.columns = [] self.selectedCats = [] - self.lastTurnSelectedCats = [] + self.lastTurnSelectedCats = [] # just temporary, for comparation self.parent = parent self.qlayer = None @@ -193,11 +193,16 @@ def OnItemSelected(self, event): self.currentItem = event.m_itemIndex - self.log.write('OnItemSelected: "%s", "%s"\n' % - (self.currentItem, - self.GetItemText(self.currentItem))) - self.selectedCats.append(self.GetItemText(self.currentItem)) + #self.log.write('OnItemSelected: "%s", "%s"\n' % + # (self.currentItem, + # self.GetItemText(self.currentItem))) + # now the funny part: + # make 1,2,3,4 to 1-4 + + self.selectedCats.append(int(self.GetItemText(self.currentItem))) + self.selectedCats.sort() + event.Skip() def RedrawMap(self): @@ -212,9 +217,42 @@ if self.lastTurnSelectedCats[:] != self.selectedCats[:]: if self.qlayer: self.map.delLayer(item='qlayer') + cats = self.selectedCats + catstr = "" + i = 0 + while 1: + next = 0 + j = 0 + while 1: + try: + if cats[i+j]+1 == cats[i+j+1]: + next +=1 + else: + break + except IndexError: + next = 0 + if j+i >= len(cats)-2: + break + else: + j += 1 + if next > 1: + catstr += "%d-%d," % (cats[i], cats[i+next]) + i += next + else: + catstr += "%d," % (cats[i]) + + i += 1 + if i >= len(cats): + break + + if catstr[-1] == ",": + catstr = string.join(catstr[:-1],"") + + # FIXME: width=1, because of maybe bug in PNG driver elusion # should be width=3 or something like this - cmd = "d.vect map=%s color=yellow fcolor=yellow cats=%s width=1" % (self.vectmap, string.join(self.selectedCats,sep=",")) + cmd = "d.vect map=%s color=yellow fcolor=yellow cats=%s width=1" % (self.vectmap, catstr) + print cmd if self.icon: cmd = cmd +" icon=%s" % (self.icon) if self.pointsize: cmd = cmd + " size=%s" % (self.pointsize) @@ -234,8 +272,9 @@ return item.GetText() def OnItemDeselected(self, event): - self.log.write("OnItemDeselected: %s" % event.m_itemIndex) - self.selectedCats.remove(self.GetItemText(event.m_itemIndex)) + #self.log.write("OnItemDeselected: %s" % event.m_itemIndex) + self.selectedCats.remove(int(self.GetItemText(event.m_itemIndex))) + self.selectedCats.sort() event.Skip() From cepicky at grass.itc.it Thu Apr 5 11:42:40 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Thu Apr 5 11:42:41 2007 Subject: [grass-addons] r443 - trunk/grassaddons/gui/gui_modules Message-ID: <200704050942.l359ge2A027854@grass.itc.it> Author: cepicky Date: 2007-04-05 11:42:40 +0200 (Thu, 05 Apr 2007) New Revision: 443 Modified: trunk/grassaddons/gui/gui_modules/dbm.py Log: scrolling to selection works Modified: trunk/grassaddons/gui/gui_modules/dbm.py =================================================================== --- trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-05 08:06:47 UTC (rev 442) +++ trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-05 09:42:40 UTC (rev 443) @@ -26,6 +26,7 @@ import wx.lib.mixins.listctrl as listmix import sys,os,locale,string +import grassenv try: from subprocess import * @@ -54,6 +55,8 @@ self.log=log self.vectmap = vectmap + if not "@" in self.vectmap: + self.vectmap = self.vectmap+"@"+grassenv.env["MAPSET"] self.mapname, self.mapset = self.vectmap.split("@") self.layer,self.tablename, self.column, self.database, self.driver =\ os.popen("v.db.connect -g map=%s" %\ @@ -252,7 +255,7 @@ # FIXME: width=1, because of maybe bug in PNG driver elusion # should be width=3 or something like this cmd = "d.vect map=%s color=yellow fcolor=yellow cats=%s width=1" % (self.vectmap, catstr) - print cmd + #print cmd if self.icon: cmd = cmd +" icon=%s" % (self.icon) if self.pointsize: cmd = cmd + " size=%s" % (self.pointsize) @@ -388,8 +391,11 @@ item = self.GetItem(idx, 0) if item.GetText() == category: #print idx - self.Select(idx,True) + #self.Select(idx,True) + self.EnsureVisible( idx ) + self.SetItemState(idx, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED) else: + #self.SetItemState(idx, wx.LIST_STATE_DESELECTED, wx.LIST_STATE_DESELECTED) self.Select(idx,False) From landa at grass.itc.it Thu Apr 5 12:29:47 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Thu Apr 5 12:29:48 2007 Subject: [grass-addons] r444 - trunk/grassaddons/gui/gui_modules Message-ID: <200704051029.l35ATlNj028361@grass.itc.it> Author: landa Date: 2007-04-05 12:29:47 +0200 (Thu, 05 Apr 2007) New Revision: 444 Modified: trunk/grassaddons/gui/gui_modules/render.py Log: __main__ test fixed Modified: trunk/grassaddons/gui/gui_modules/render.py =================================================================== --- trunk/grassaddons/gui/gui_modules/render.py 2007-04-05 09:42:40 UTC (rev 443) +++ trunk/grassaddons/gui/gui_modules/render.py 2007-04-05 10:29:47 UTC (rev 444) @@ -682,6 +682,7 @@ layer = MapLayer(type="raster", name=name, mapset=mapset, active=l_active, hidden=l_hidden, opacity=l_opacity, dispcmd=dispcmd) + layer.id = len(self.layers)-1 # add maplayer to the list of layers @@ -1069,7 +1070,7 @@ Go trough all layers and remove them from layer list Removes also l_mapfile and l_maskfile - Returns 1 if failed or None if ok" + Returns 1 if failed or None if ok """ try: for layer in self.layers: @@ -1109,15 +1110,15 @@ map.width = 300 map.height = 400 - map.AddRasterLayer("elevation.dem", mapset="PERMANENT", catlist="1000-1500", invertCats=True) + map.AddRasterLayer(name="elevation.dem", mapset="PERMANENT", dispcmd = {"catlist" : "1000-1500", "i" : None}, + l_opacity=.7) - map.AddVectorLayer("roads", color="red", width=3, mapset="PERMANENT", - l_opacity=50) + map.AddVectorLayer(name="streams", mapset="PERMANENT", dispcmd = {"color" : "red", "width" : 3, "type" : "line"}) image = map.Render(force=True) if image: - os.system("display %s" % image) + os.system("display %s" % image[0]) #image = map.Render() #os.system("display %s" % image) From cepicky at grass.itc.it Thu Apr 5 13:50:30 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Thu Apr 5 13:50:31 2007 Subject: [grass-addons] r445 - trunk/grassaddons/gui/gui_modules Message-ID: <200704051150.l35BoU8C029453@grass.itc.it> Author: cepicky Date: 2007-04-05 13:50:30 +0200 (Thu, 05 Apr 2007) New Revision: 445 Modified: trunk/grassaddons/gui/gui_modules/select.py Log: fixed (coomented out) Expand of map layer tree, which causes Exception in gis manager Modified: trunk/grassaddons/gui/gui_modules/select.py =================================================================== --- trunk/grassaddons/gui/gui_modules/select.py 2007-04-05 10:29:47 UTC (rev 444) +++ trunk/grassaddons/gui/gui_modules/select.py 2007-04-05 11:50:30 UTC (rev 445) @@ -84,7 +84,7 @@ pass #mapsets in current location - mapsets = os.popen('g.mapsets -p', "r").read().lstrip().rstrip().split(' ') + mapsets = os.popen('g.mapsets -p').read().strip().split(' ') elementlist = ['cell', 'grid3d', @@ -106,7 +106,6 @@ #Get directory tree nodes for dir in mapsets: if dir == curr_mapset: - #TODO: make current mapset node expanded dir_node = self.AddItem('Mapset: '+dir) self.tree.SetItemTextColour(dir_node,wx.Colour(50,50,200)) try: @@ -115,7 +114,15 @@ self.AddItem(elem+'@'+dir, parent=dir_node) except: continue - self.tree.Expand(dir_node) + # FIXME: This introduces error and so it is commented out + # -------- ERROR BEGIN -------------- + # Traceback (most recent call last): + # File "/hardmnt/moll0/ssi/cepicky/src/gis/grass/grass6/dist.x86_64-unknown-linux-gnu/etc/wx/gui_modules/wxgui_utils.py", line 348, in onExpandNode + # if self.layertype[self.layer_selected] == 'group': + # KeyError: > + # -------- ERROR END -------------- + #self.tree.Expand(dir_node) + else: dir_node = self.AddItem('Mapset: '+dir) self.tree.SetItemTextColour(dir_node,wx.Colour(50,50,200)) From neteler at grass.itc.it Thu Apr 5 14:40:46 2007 From: neteler at grass.itc.it (neteler@grass.itc.it) Date: Thu Apr 5 14:40:47 2007 Subject: [grass-addons] r446 - trunk/grassaddons/gui Message-ID: <200704051240.l35Cekxb029711@grass.itc.it> Author: neteler Date: 2007-04-05 14:40:45 +0200 (Thu, 05 Apr 2007) New Revision: 446 Modified: trunk/grassaddons/gui/README Log: Mandriva 2007 wxPython RPMs available Modified: trunk/grassaddons/gui/README =================================================================== --- trunk/grassaddons/gui/README 2007-04-05 11:50:30 UTC (rev 445) +++ trunk/grassaddons/gui/README 2007-04-05 12:40:45 UTC (rev 446) @@ -7,6 +7,14 @@ --------------------------------------------------------------------- Requirements: Python >=2.4 and wxPython 2.8.1.1 + + Get wxPython 2.8.x packages from: + * Debian: + * Fedora: + * Mandriva 2007: http://mpa.itc.it/markus/wxpython_rpms/ + * MS-Windows: + * SuSe: + * Ubuntu: --------------------------------------------------------------------- How to start: From cepicky at grass.itc.it Thu Apr 5 15:45:57 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Thu Apr 5 15:45:59 2007 Subject: [grass-addons] r447 - trunk/grassaddons/gui Message-ID: <200704051345.l35DjvLj030059@grass.itc.it> Author: cepicky Date: 2007-04-05 15:45:57 +0200 (Thu, 05 Apr 2007) New Revision: 447 Modified: trunk/grassaddons/gui/README Log: updated sources Modified: trunk/grassaddons/gui/README =================================================================== --- trunk/grassaddons/gui/README 2007-04-05 12:40:45 UTC (rev 446) +++ trunk/grassaddons/gui/README 2007-04-05 13:45:57 UTC (rev 447) @@ -9,12 +9,13 @@ Python >=2.4 and wxPython 2.8.1.1 Get wxPython 2.8.x packages from: - * Debian: + * Debian: http://www.bitpim.org/developer.html -> "Install wxPython" * Fedora: * Mandriva 2007: http://mpa.itc.it/markus/wxpython_rpms/ * MS-Windows: * SuSe: - * Ubuntu: + * Ubuntu: deb http://wxpython.wxcommunity.com/apt/ubuntu/dapper / + deb http://wxpython.wxcommunity.com/apt/ubuntu/feisty / --------------------------------------------------------------------- How to start: From barton at grass.itc.it Fri Apr 6 02:15:45 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Fri Apr 6 02:15:46 2007 Subject: [grass-addons] r448 - trunk/grassaddons/gui/gui_modules Message-ID: <200704060015.l360Fjli002530@grass.itc.it> Author: barton Date: 2007-04-06 02:15:36 +0200 (Fri, 06 Apr 2007) New Revision: 448 Modified: trunk/grassaddons/gui/gui_modules/dbm.py Log: set column size to width of header Modified: trunk/grassaddons/gui/gui_modules/dbm.py =================================================================== --- trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-05 13:45:57 UTC (rev 447) +++ trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-06 00:15:36 UTC (rev 448) @@ -115,7 +115,7 @@ self.columns.append({"name":column,"type":str}) self.InsertColumn(i, column) - self.SetColumnWidth(i, 50) + self.SetColumnWidth(i, wx.LIST_AUTOSIZE_USEHEADER) i += 1 if i >= 256: self.log.write("Can display only 256 columns") @@ -206,7 +206,7 @@ self.selectedCats.append(int(self.GetItemText(self.currentItem))) self.selectedCats.sort() - event.Skip() + event.Skip() def RedrawMap(self): @@ -250,8 +250,8 @@ if catstr[-1] == ",": catstr = string.join(catstr[:-1],"") - + # FIXME: width=1, because of maybe bug in PNG driver elusion # should be width=3 or something like this cmd = "d.vect map=%s color=yellow fcolor=yellow cats=%s width=1" % (self.vectmap, catstr) @@ -268,7 +268,7 @@ self.currentItem = event.m_itemIndex self.log.write("OnItemActivated: %s\nTopItem: %s\n" % (self.GetItemText(self.currentItem), self.GetTopItem())) - event.Skip() + event.Skip() def getColumnText(self, index, col): item = self.GetItem(index, col) @@ -278,7 +278,7 @@ #self.log.write("OnItemDeselected: %s" % event.m_itemIndex) self.selectedCats.remove(int(self.GetItemText(event.m_itemIndex))) self.selectedCats.sort() - event.Skip() + event.Skip() #--------------------------------------------------- @@ -386,7 +386,7 @@ category = line.strip().split(" ")[1] #print category - + for idx in range(self.GetItemCount()): item = self.GetItem(idx, 0) if item.GetText() == category: @@ -434,7 +434,6 @@ - event.Skip() @@ -473,7 +472,6 @@ - event.Skip() @@ -483,7 +481,6 @@ - #---------------------------------------------------------------------- # The main window #---------------------------------------------------------------------- From barton at grass.itc.it Fri Apr 6 07:25:12 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Fri Apr 6 07:25:14 2007 Subject: [grass-addons] r449 - trunk/grassaddons/gui/gui_modules Message-ID: <200704060525.l365PCSV005594@grass.itc.it> Author: barton Date: 2007-04-06 07:25:01 +0200 (Fri, 06 Apr 2007) New Revision: 449 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py trunk/grassaddons/gui/gui_modules/wxgui_utils.py Log: Fixed bug in mapdisplay query. Made nicer raster query output. Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-06 00:15:36 UTC (rev 448) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-06 05:25:01 UTC (rev 449) @@ -584,7 +584,7 @@ # digitizing elif self.parent.digittoolbar: if self.parent.digittoolbar.digitize == "point": - east,north= self.Pixel2Cell(self.mouse['end'][0],self.mouse['end'][1]) + east,north= self.Pixel2Cell(self.mouse['begin'][0],self.mouse['begin'][1]) self.parent.digittoolbar.AddPoint(east,north) # redraw map self.render=True @@ -592,12 +592,17 @@ # quering elif self.mouse["box"] == "query": - east,north = self.Pixel2Cell(self.mouse['end'][0],self.mouse['end'][1]) + east,north = self.Pixel2Cell(self.mouse['begin'][0],self.mouse['begin'][1]) if self.parent.gismanager: layer = self.parent.gismanager.maptree.GetSelection() type = self.parent.gismanager.maptree.layertype[layer] - map,mapset = layer.GetText().split("@") - self.parent.QueryMap(map,mapset,type,east,north) + dcmd = self.parent.gismanager.maptree.GetPyData(layer)[0] + mapname = None + for item in dcmd.split(' '): + if 'map=' in item: + mapname = item.split('=')[1] + + self.parent.QueryMap(mapname,type,east,north) else: print "Quering without gis manager not implemented yet" @@ -661,7 +666,6 @@ newy = self.Map.region['n'] - y * self.Map.region["nsres"] return newx, newy - def Zoom(self, begin, end, zoomtype): """ Calculates new region while (un)zoom/pan-ing @@ -817,7 +821,6 @@ pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE, toolbars=["map"], cb=None, idx=-1): -# style=wx.DEFAULT_FRAME_STYLE, toolbars=["map"]): """ Main map display window with toolbars, statusbar and @@ -1137,17 +1140,20 @@ # change the cursor self.MapWindow.SetCursor (self.cursors["cross"]) - def QueryMap(self,name,mapset,type,x,y): + def QueryMap(self,mapname,type,x,y): """ Run *.what command in gis manager output window """ + #set query snap distance for v.what at mapunit equivalent of 10 pixels + qdist = 10.0 * ((self.Map.region['e'] - self.Map.region['w'])/self.Map.width) + if type == "raster": - cmd = "r.what input=%s east_north=%f,%f" %\ - (name+"@"+mapset, float(x), float(y)) + cmd = "r.what -f input=%s east_north=%f,%f" %\ + (mapname, float(x), float(y)) elif type == "vector": - cmd = "v.what map=%s east_north=%f,%f"%\ - (name+"@"+mapset, float(x), float(y)) + cmd = "v.what -a map=%s east_north=%f,%f distance=%f" %\ + (mapname, float(x), float(y), qdist) if self.gismanager: self.gismanager.goutput.runCmd(cmd) Modified: trunk/grassaddons/gui/gui_modules/wxgui_utils.py =================================================================== --- trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-06 00:15:36 UTC (rev 448) +++ trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-06 05:25:01 UTC (rev 449) @@ -813,10 +813,18 @@ oline = p.stdout.readline() while oline: oline = oline.strip() - self.cmd_output.write(oline+"\n") + if cmd.split(' ')[0] == 'r.what': + rastqlist = oline.split('|') + self.cmd_output.write('East: '+rastqlist[0]+"\n") + self.cmd_output.write('North: '+rastqlist[1]+"\n") + self.cmd_output.write(rastqlist[2]+"\n") + self.cmd_output.write('Category: '+rastqlist[3]+"\n") + self.cmd_output.write('Label: '+rastqlist[4]+"\n") + else: + self.cmd_output.write(oline+"\n") print >> sys.stderr, oline oline = p.stdout.readline() - + self.cmd_output.write("\n==========\n") if p.stdout < 0: print >> sys.stderr, "Child was terminated by signal", p.stdout elif p.stdout > 0: From barton at grass.itc.it Fri Apr 6 09:34:01 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Fri Apr 6 09:34:03 2007 Subject: [grass-addons] r450 - trunk/grassaddons/gui/gui_modules Message-ID: <200704060734.l367Y1xH007679@grass.itc.it> Author: barton Date: 2007-04-06 09:33:52 +0200 (Fri, 06 Apr 2007) New Revision: 450 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py Log: Adding text overlay. Everything working except that there is no text drawn??? Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-06 05:25:01 UTC (rev 449) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-06 07:33:52 UTC (rev 450) @@ -319,11 +319,12 @@ self.ovlcoords[drawid] = coords elif pdctype == 'text': # draw text on top of map - text = img + print 'in draw: font info, id, pdctype = ',img,drawid,pdctype + text = img[0] w,h = self.GetFullTextExtent(text)[0:2] - pdc.SetFont(self.GetFont()) - pdc.SetTextForeground(self.RandomColor()) - pdc.SetTextBackground(self.RandomColor()) + pdc.SetFont(img[1]) + pdc.SetTextForeground(img[2]) +# pdc.SetTextBackground(self.RandomColor()) pdc.DrawText(text, coords[0], coords[1]) pdc.SetIdBounds(drawid, (coords[0], coords[1], coords[2], coords[3])) self.ovlcoords[drawid] = coords @@ -1184,6 +1185,14 @@ decmenu.AppendItem(addlegend) self.Bind(wx.EVT_MENU, self.addLegend, addlegend) + addtext = wx.MenuItem(decmenu, -1,'Text') + bmp = wx.Image(os.path.join(icons,'gui-font.gif'), wx.BITMAP_TYPE_GIF) + bmp.Rescale(16, 16) + bmp = bmp.ConvertToBitmap() + addtext.SetBitmap(bmp) + decmenu.AppendItem(addtext) + self.Bind(wx.EVT_MENU, self.addText, addtext) + # Popup the menu. If an item is selected then its handler # will be called before PopupMenu returns. self.PopupMenu(decmenu) @@ -1269,6 +1278,35 @@ self.MapWindow.UpdateMap() dlg.Destroy() + def addText(self, event): + """ + Handler for text decoration menu selection. + """ + ovltype = 2 # index for overlay layer in render + + id = wx.NewId()+100 + print 'in addText' + + dlg = TextDialog(self, wx.ID_ANY, 'Text', size=(350, 200), + style=wx.DEFAULT_DIALOG_STYLE, + ovltype=ovltype, + drawid=id) + + dlg.CenterOnScreen() + + # If OK button pressed in decoration control dialog + val = dlg.ShowModal() + if val == wx.ID_OK: + print 'OK' + maptext = dlg.currText + textfont = dlg.currFont + textcolor = dlg.currClr + print 'text, font, color =', maptext,textfont,textcolor + + self.MapWindow.Draw(self.MapWindow.pdc, img=(maptext,textfont,textcolor), drawid=id, pdctype='text') + + + def getOptData(self, dcmd, type, params): """ Callback method for decoration overlay command generated by @@ -1352,6 +1390,121 @@ completed=(self.Parent.getOptData,self.ovltype,self.params), parentframe=None) +class TextDialog(wx.Dialog): + def __init__(self, parent, id, title, pos=wx.DefaultPosition, size=wx.DefaultSize, + style=wx.DEFAULT_DIALOG_STYLE, + ovltype=2,drawid=None,currText='', + currClr=wx.BLACK, + currFont=''): + wx.Dialog.__init__(self, parent, id, title, pos, size, style) + """ + Controls setting options and displaying/hiding map overlay decorations + """ + print 'in textdialog' + + self.ovltype = ovltype + self.drawid = drawid + self.currClr = currClr + self.currText = currText + self.currFont = currFont +# self.ovlcmd = cmd +# self.ovlchk = self.Parent.MapWindow.ovlchk +# self.params = params #previously set decoration options to pass back to options dialog + + sizer = wx.BoxSizer(wx.VERTICAL) + + box = wx.BoxSizer(wx.HORIZONTAL) + + label = wx.StaticText(self, -1, "Enter text:") + box.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5) + + self.textentry = wx.TextCtrl(self, -1, "", size=(80,-1)) + self.currFont = self.textentry.GetFont() + box.Add(self.textentry, 0, wx.ALIGN_CENTRE|wx.ALL, 5) + sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) + + box = wx.BoxSizer(wx.HORIZONTAL) + fontbtn = wx.Button(self, wx.ID_ANY, "Set font") + box.Add(fontbtn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) + sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) + + box = wx.BoxSizer(wx.HORIZONTAL) + label = wx.StaticText(self, -1, ("Double-click text with mouse in\npointer mode and drag to position.\nDouble-click again to set")) + box.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5) + sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) + + line = wx.StaticLine(self, -1, size=(20,-1), style=wx.LI_HORIZONTAL) + sizer.Add(line, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.RIGHT|wx.TOP, 5) + + btnsizer = wx.StdDialogButtonSizer() + + btn = wx.Button(self, wx.ID_OK) + btn.SetDefault() + btnsizer.AddButton(btn) + + btn = wx.Button(self, wx.ID_CANCEL) + btnsizer.AddButton(btn) + btnsizer.Realize() + + sizer.Add(btnsizer, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) + + self.SetSizer(sizer) + sizer.Fit(self) + +# self.Bind(wx.EVT_CHECKBOX, self.onCheck, self.chkbox) + self.Bind(wx.EVT_BUTTON, self.onSelectFont, fontbtn) + self.textentry.Bind(wx.EVT_TEXT, self.onText) + + +# def onCheck(self, event): +# """ +# Handler for checkbox for displaying/hiding decoration +# """ +# check = event.IsChecked() +# self.ovlchk[self.drawid] = check + + def onText(self, event): + self.currText = event.GetString() + + def onSelectFont(self, event): + data = wx.FontData() + data.EnableEffects(True) + data.SetColour(self.currClr) # set colour + data.SetInitialFont(self.currFont) + + dlg = wx.FontDialog(self, data) + + if dlg.ShowModal() == wx.ID_OK: + data = dlg.GetFontData() + font = data.GetChosenFont() + face = font.GetFaceName() + ptsize = font.GetPointSize() + colour = data.GetColour() + + print ('You selected: "%s", %d points, color %s\n' % + (font.GetFaceName(), font.GetPointSize(), + colour.Get())) + + self.currFont = font + self.currClr = colour + self.UpdateUI() + + # Don't destroy the dialog until you get everything you need from the + # dialog! + dlg.Destroy() + + def UpdateUI(self): + self.textentry.SetFont(self.currFont) + self.textentry.SetForegroundColour(self.currClr) +# self.ps.SetLabel(str(self.currFont.GetPointSize())) +# self.family.SetLabel(self.currFont.GetFamilyString()) +# self.style.SetLabel(self.currFont.GetStyleString()) +# self.weight.SetLabel(self.currFont.GetWeightString()) +# self.face.SetLabel(self.currFont.GetFaceName()) +# self.nfi.SetLabel(self.currFont.GetNativeFontInfo().ToString()) + self.Layout() + + class MapApp(wx.App): """ MapApp class From barton at grass.itc.it Fri Apr 6 09:34:37 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Fri Apr 6 09:34:38 2007 Subject: [grass-addons] r451 - trunk/grassaddons/gui Message-ID: <200704060734.l367YbLC007701@grass.itc.it> Author: barton Date: 2007-04-06 09:34:28 +0200 (Fri, 06 Apr 2007) New Revision: 451 Modified: trunk/grassaddons/gui/wxgui.py Log: Legend is overlay, not layer Modified: trunk/grassaddons/gui/wxgui.py =================================================================== --- trunk/grassaddons/gui/wxgui.py 2007-04-06 07:33:52 UTC (rev 450) +++ trunk/grassaddons/gui/wxgui.py 2007-04-06 07:34:28 UTC (rev 451) @@ -423,14 +423,6 @@ rastmenu.AppendItem(addhis) self.Bind(wx.EVT_MENU, self.addHIS, addhis) - addrleg = wx.MenuItem(rastmenu, -1,'Add raster legend layer') - bmp = wx.Image(os.path.join(wxgui_utils.icons,'module-d.legend.gif'), wx.BITMAP_TYPE_GIF) - bmp.Rescale(16, 16) - bmp = bmp.ConvertToBitmap() - addrleg.SetBitmap(bmp) - rastmenu.AppendItem(addrleg) - self.Bind(wx.EVT_MENU, self.addRastLeg, addrleg) - # Popup the menu. If an item is selected then its handler # will be called before PopupMenu returns. self.PopupMenu(rastmenu) From landa at grass.itc.it Fri Apr 6 14:05:55 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Fri Apr 6 14:05:57 2007 Subject: [grass-addons] r452 - trunk/grassaddons/gui Message-ID: <200704061205.l36C5tF7011620@grass.itc.it> Author: landa Date: 2007-04-06 14:05:55 +0200 (Fri, 06 Apr 2007) New Revision: 452 Modified: trunk/grassaddons/gui/README Log: cosmetics; p.cmd note Modified: trunk/grassaddons/gui/README =================================================================== --- trunk/grassaddons/gui/README 2007-04-06 07:34:28 UTC (rev 451) +++ trunk/grassaddons/gui/README 2007-04-06 12:05:55 UTC (rev 452) @@ -1,8 +1,8 @@ GRASS graphical user interface ============================== -AUTHORS: Jachym, Michael -LAST CHANGE: 2007-3-25 +AUTHORS: Jachym, Michael, MartinL +LAST CHANGE: 2007-04-06 --------------------------------------------------------------------- Requirements: @@ -20,9 +20,7 @@ How to start: -Before you start ----------------- -Download fresh source from subversion: +Download fresh source code from subversion: $~ svn co https://grasssvn.itc.it/svn/grassaddons/trunk/grassaddons/gui grassaddons/gui Or update your local repository @@ -33,16 +31,19 @@ $~ cd grassaddons/gui + 1 - INSTALLATION -Put everthing from gui into a new directory $GISBASE/etc/wx. +Put everything from gui into a new directory $GISBASE/etc/wx. Move the script "wxgrass" into $GISBASE/scripts 2 - STARTUP WITH GRASS INITIALIZATION -If you want to start the wxgrass GUI automatically when you start GRASS, replace your $GRASS/etc/init.sh with the one that accompanies wxgrass. Rename your original init.sh to avoid overwriting it. +If you want to start the wxgrass GUI automatically when you start +GRASS, replace your $GRASS/etc/init.sh with the one that accompanies +wxgrass. Rename your original init.sh to avoid overwriting it. Edit your .grassrc6 file to replace... @@ -58,25 +59,27 @@ 3 - STARTUP FROM GRASS TERMINAL -Do not use the wxgrass init.sh. Simply type wxgrass& from the GRASS terminal to start the GUI using the wxgrass script. +Do not use the wxgrass init.sh. Simply type wxgrass& from the GRASS +terminal to start the GUI using the wxgrass script. -4 - CLI MapDisplay scripts ------------------- -This is going to be replacement for command line tools like d.rast and d.vect +4 - CLI Display scripts -Now add directory "scripts" in gui directory to your $PATH. This small -programms should later go to GRASS Scripts directory or became Python -scripts or C programs. Now they are small bash wrappers, which can be -easily and fast costumized. +This is going to be replacement for command line tools like d.rast and +d.vect. +Now add directory "scripts" in gui directory to your $PATH. These +little programs should later go to GRASS Scripts directory or became +Python scripts or C programs. Now they are only small BASH wrappers +which can be easily and fast customized. + $~ export PATH=scripts/:$PATH -Lounch GRASS, if you didn't it yet +Start GRASS: $~ grass63 ~/grassdata/spearfish60/user1 -Use command p.mon (shell script in gui/scripts direcotory) to start map +Use command p.mon (shell script in gui/scripts directory) to start map display: GRASS> p.mon anything @@ -95,10 +98,13 @@ GRASS> p.vect roads NOTE: only map name is currently supported. No other option will influence - the map apperience + the map layout +Alternatively you can use more universal p.cmd: -You should be abple to zoom && pan through the map, ones the layers are +GRASS> p.cmd "d.rast map=elevation.dem@PERMANENT catlist=1300-1400 -i" + +You should be able to zoom && pan through the map, once the layers are displayed. You should be also able to store the display content as well as clear the display and start from scratch. From chemin at grass.itc.it Fri Apr 6 17:22:00 2007 From: chemin at grass.itc.it (chemin@grass.itc.it) Date: Fri Apr 6 17:22:02 2007 Subject: [grass-addons] r453 - in trunk/grassaddons/gipe: . r.evapo.MH Message-ID: <200704061522.l36FM03R013048@grass.itc.it> Author: chemin Date: 2007-04-06 17:21:52 +0200 (Fri, 06 Apr 2007) New Revision: 453 Added: trunk/grassaddons/gipe/r.evapo.MH/ trunk/grassaddons/gipe/r.evapo.MH/Makefile trunk/grassaddons/gipe/r.evapo.MH/description.html trunk/grassaddons/gipe/r.evapo.MH/main.c trunk/grassaddons/gipe/r.evapo.MH/mh_eto.c trunk/grassaddons/gipe/r.evapo.MH/mh_original.c Modified: trunk/grassaddons/gipe/Makefile Log: Added r.evapo.MH: ET reference by Modified Hargreaves (+ Flag for original Hargreaves) Modified: trunk/grassaddons/gipe/Makefile =================================================================== --- trunk/grassaddons/gipe/Makefile 2007-04-06 12:05:55 UTC (rev 452) +++ trunk/grassaddons/gipe/Makefile 2007-04-06 15:21:52 UTC (rev 453) @@ -45,6 +45,7 @@ r.eb.ustar \ r.eb.z0m \ r.emissivity \ + r.evapo.MH \ r.evapo.potrad \ r.evapo.PM \ r.evapo.PT \ Added: trunk/grassaddons/gipe/r.evapo.MH/Makefile =================================================================== --- trunk/grassaddons/gipe/r.evapo.MH/Makefile (rev 0) +++ trunk/grassaddons/gipe/r.evapo.MH/Makefile 2007-04-06 15:21:52 UTC (rev 453) @@ -0,0 +1,12 @@ +MODULE_TOPDIR = ../.. + +PGM = r.evapo.MH + +LIBES = $(VECTLIB) $(RASTERLIB) $(GISLIB) +DEPENDENCIES= $(VECTDEP) $(GISDEP) $(DISPLAYDEP) $(RASTERDEP) +EXTRA_INC = $(VECT_INC) +EXTRA_CFLAGS = $(VECT_CFLAGS) + +include $(MODULE_TOPDIR)/include/Make/Module.make + +default: cmd Added: trunk/grassaddons/gipe/r.evapo.MH/description.html =================================================================== --- trunk/grassaddons/gipe/r.evapo.MH/description.html (rev 0) +++ trunk/grassaddons/gipe/r.evapo.MH/description.html 2007-04-06 15:21:52 UTC (rev 453) @@ -0,0 +1,31 @@ +

DESCRIPTION

+ +r.evapo.MH Calculates the reference ET after Hargreaves (1985) and Modified Hargreaves (2001). + +

NOTES

+Hargreaves GL, Hargreaves GH, Riley JP, 1985. Agricultural benefits for Senegal River Basin. Journal of Irrigation and Drainange Engineering, ASCE, 111(2):113-124. + +Droogers P, Allen RG, 2001. Towards a simplified global reference evapotranspiration equation. Irrigation Science. + +

TODO

+ + +

SEE ALSO

+ + +r.evapo.PT
+r.evapo.PM
+r.evapo.TSA
+r.evapo.potrad
+r.sun
+ +
+ + +

AUTHORS

+ +Yann Chemin, GRASS Development team, 2007
+ + +

+Last changed: $Date: 2007/04/10 11:58:15 $ Added: trunk/grassaddons/gipe/r.evapo.MH/main.c =================================================================== --- trunk/grassaddons/gipe/r.evapo.MH/main.c (rev 0) +++ trunk/grassaddons/gipe/r.evapo.MH/main.c 2007-04-06 15:21:52 UTC (rev 453) @@ -0,0 +1,367 @@ +/***************************************************************************** +* +* MODULE: r.evapo.MH +* AUTHOR: Yann Chemin (2007) +* yann.chemin_AT_gmail.com +* +* PURPOSE: To estimate the reference evapotranspiration by means +* of Modified Hargreaves method (2001). +* Also has a switch for original Hargreaves (1985). +* +* COPYRIGHT: (C) 2007 by the GRASS Development Team +* +* This program is free software under the GNU General Public +* Licence (>=2). Read the file COPYING that comes with GRASS +* for details. +* +***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + + +//proto ET +double mh_original(double ra,double tavg,double tmax,double tmin,double p); +double mh_eto(double ra,double tavg,double tmax,double tmin,double p); + + +int main(int argc, char *argv[]) +{ + /* buffer for input-output rasters */ + void *inrast_TEMPKAVG,*inrast_TEMPKMIN, *inrast_TEMPKMAX, *inrast_RNET,*inrast_P; + + unsigned char *outrast; + + /* pointers to input-output raster files */ + int infd_TEMPKAVG,infd_TEMPKMIN,infd_TEMPKMAX,infd_RNET,infd_P; + + int outfd; + + /* mapsets for input raster files */ + char *mapset_TEMPKAVG,*mapset_TEMPKMIN,*mapset_TEMPKMAX,*mapset_RNET,*mapset_P; + + /* names of input-output raster files */ + char *RNET, *TEMPKAVG, *TEMPKMIN, *TEMPKMAX, *P; + + char *ETa; + + /* input-output cell values */ + DCELL d_tempkavg, d_tempkmin, d_tempkmax, d_rnet, d_p; + DCELL d_daily_et; + + + /* region informations and handler */ + struct Cell_head cellhd; + int nrows, ncols; + int row, col; + + /* parser stuctures definition */ + struct GModule *module; + struct Option *input_RNET,*input_TEMPKAVG, *input_TEMPKMIN; + struct Option *input_TEMPKMAX, *input_P; + struct Option *output; + struct Flag *flag1, *zero, *original; + struct Colors color; + struct History history; + + RASTER_MAP_TYPE data_type_output=DCELL_TYPE; + RASTER_MAP_TYPE data_type_tempkavg; + RASTER_MAP_TYPE data_type_tempkmin; + RASTER_MAP_TYPE data_type_tempkmax; + RASTER_MAP_TYPE data_type_rnet; + RASTER_MAP_TYPE data_type_p; + RASTER_MAP_TYPE data_type_eta; + + /* Initialize the GIS calls */ + G_gisinit(argv[0]); + + module = G_define_module(); + module->description = + _("Evapotranspiration Calculation " + "Modified Hargreaves formulation, 2001." + "Flag for Original Hargreaves (1985)."); + + /* Define different options */ + input_RNET = G_define_option(); + input_RNET->key = "RNETD"; + input_RNET->key_desc = "[W/m2/d]"; + input_RNET->type = TYPE_STRING; + input_RNET->required = YES; + input_RNET->gisprompt = "old,cell,raster"; + input_RNET->description = _("Name of Diurnal Net Radiation raster map"); + + input_TEMPKAVG = G_define_option(); + input_TEMPKAVG->key = "TEMPKAVG"; + input_TEMPKAVG->key_desc = "[C]"; + input_TEMPKAVG->type = TYPE_STRING; + input_TEMPKAVG->required = YES; + input_TEMPKAVG->gisprompt = "old,cell,raster"; + input_TEMPKAVG->description = _("Name of avg air temperature raster map"); + + input_TEMPKMIN = G_define_option(); + input_TEMPKMIN->key = "TEMPKMIN"; + input_TEMPKMIN->key_desc = "[C]"; + input_TEMPKMIN->type = TYPE_STRING; + input_TEMPKMIN->required = YES; + input_TEMPKMIN->gisprompt = "old,cell,raster"; + input_TEMPKMIN->description = _("Name of min air temperature raster map"); + + input_TEMPKMAX = G_define_option(); + input_TEMPKMAX->key = "TEMPKMAX"; + input_TEMPKMAX->key_desc = "[C]"; + input_TEMPKMAX->type = TYPE_STRING; + input_TEMPKMAX->required = YES; + input_TEMPKMAX->gisprompt = "old,cell,raster"; + input_TEMPKMAX->description = _("Name of max air temperature raster map"); + + input_P = G_define_option(); + input_P->key = "P"; + input_P->key_desc = "[mm/month]"; + input_P->type = TYPE_STRING; + input_P->required = NO; + input_P->gisprompt = "old,cell,raster"; + input_P->description = _("Name of precipitation raster map, disabled if original Hargreaves (1985) is enabled."); + + output = G_define_option() ; + output->key = "output"; + output->key_desc = "[mm/d]"; + output->type = TYPE_STRING; + output->required = YES; + output->gisprompt = "new,cell,raster" ; + output->description = _("Name of output Ref Evapotranspiration layer"); + + /* Define the different flags */ + flag1 = G_define_flag() ; + flag1->key = 'q' ; + flag1->description = _("quiet"); + + zero = G_define_flag() ; + zero->key = 'z' ; + zero->description = _("set negative ETa to zero"); + + original = G_define_flag() ; + original->key = 'h' ; + original->description = _("set to original Hargreaves (1985)"); + + if (G_parser(argc, argv)) + exit(EXIT_FAILURE); + + /* get entered parameters */ + RNET = input_RNET->answer; + TEMPKAVG = input_TEMPKAVG->answer; + TEMPKMIN = input_TEMPKMIN->answer; + TEMPKMAX = input_TEMPKMAX->answer; + P = input_P->answer; + + ETa = output->answer; + + /* find maps in mapset */ + mapset_RNET = G_find_cell2 (RNET, ""); + if (mapset_RNET == NULL) + G_fatal_error (_("cell file [%s] not found"), RNET); + mapset_TEMPKAVG = G_find_cell2 (TEMPKAVG, ""); + if (mapset_TEMPKAVG == NULL) + G_fatal_error (_("cell file [%s] not found"), TEMPKAVG); + mapset_TEMPKMIN = G_find_cell2 (TEMPKMIN, ""); + if (mapset_TEMPKMIN == NULL) + G_fatal_error (_("cell file [%s] not found"), TEMPKMIN); + mapset_TEMPKMAX = G_find_cell2 (TEMPKMAX, ""); + if (mapset_TEMPKMAX == NULL) + G_fatal_error (_("cell file [%s] not found"), TEMPKMAX); + if(!original->answer){ + mapset_P = G_find_cell2 (P, ""); + if (mapset_P == NULL) + G_fatal_error (_("cell file [%s] not found"), P); + } + /* check legal output name */ + if (G_legal_filename (ETa) < 0) + G_fatal_error (_("[%s] is an illegal name"), ETa); + + /* determine the input map type (CELL/FCELL/DCELL) */ + data_type_rnet = G_raster_map_type(RNET,mapset_RNET); + data_type_tempkavg = G_raster_map_type(TEMPKAVG,mapset_TEMPKAVG); + data_type_tempkmin = G_raster_map_type(TEMPKMIN,mapset_TEMPKMIN); + data_type_tempkmax = G_raster_map_type(TEMPKMAX,mapset_TEMPKMAX); + if(!original->answer){ + data_type_p = G_raster_map_type(P,mapset_P); + } + /* open pointers to input raster files */ + if ( (infd_RNET = G_open_cell_old (RNET, mapset_RNET)) < 0) + G_fatal_error (_("Cannot open cell file [%s]"), RNET); + if ( (infd_TEMPKAVG = G_open_cell_old (TEMPKAVG, mapset_TEMPKAVG)) < 0) + G_fatal_error (_("Cannot open cell file [%s]"), TEMPKAVG); + if ( (infd_TEMPKMIN = G_open_cell_old (TEMPKMIN, mapset_TEMPKMIN)) < 0) + G_fatal_error (_("Cannot open cell file [%s]"), TEMPKMIN); + if ( (infd_TEMPKMAX = G_open_cell_old (TEMPKMAX, mapset_TEMPKMAX)) < 0) + G_fatal_error (_("Cannot open cell file [%s]"), TEMPKMAX); + if(!original->answer){ + if ( (infd_P = G_open_cell_old (P, mapset_P)) < 0) + G_fatal_error (_("Cannot open cell file [%s]"), P); + } + /* read headers of raster files */ + if (G_get_cellhd (RNET, mapset_RNET, &cellhd) < 0) + G_fatal_error (_("Cannot read file header of [%s]"), RNET); + if (G_get_cellhd (TEMPKAVG, mapset_TEMPKAVG, &cellhd) < 0) + G_fatal_error (_("Cannot read file header of [%s]"), TEMPKAVG); + if (G_get_cellhd (TEMPKMIN, mapset_TEMPKMIN, &cellhd) < 0) + G_fatal_error (_("Cannot read file header of [%s]"), TEMPKMIN); + if (G_get_cellhd (TEMPKMAX, mapset_TEMPKMAX, &cellhd) < 0) + G_fatal_error (_("Cannot read file header of [%s]"), TEMPKMAX); + if(!original->answer){ + if (G_get_cellhd (P, mapset_P, &cellhd) < 0) + G_fatal_error (_("Cannot read file header of [%s]"), P); + } + /* Allocate input buffer */ + inrast_RNET = G_allocate_raster_buf(data_type_rnet); + inrast_TEMPKAVG = G_allocate_raster_buf(data_type_tempkavg); + inrast_TEMPKMIN = G_allocate_raster_buf(data_type_tempkmin); + inrast_TEMPKMAX = G_allocate_raster_buf(data_type_tempkmax); + if(!original->answer){ + inrast_P = G_allocate_raster_buf(data_type_p); + } + /* get rows and columns number of the current region */ + nrows = G_window_rows(); + ncols = G_window_cols(); + + /* allocate output buffer */ + outrast = G_allocate_raster_buf(data_type_output); + + /* open pointers to output raster files */ + if ( (outfd = G_open_raster_new (ETa,data_type_output)) < 0) + G_fatal_error (_("Could not open <%s>"),ETa); + + + /* start the loop through cells */ + for (row = 0; row < nrows; row++) + { + + /* read input raster row into line buffer*/ + if (G_get_raster_row (infd_RNET, inrast_RNET, row,data_type_rnet) < 0) + G_fatal_error (_("Could not read from <%s>"),RNET); + if (G_get_raster_row (infd_TEMPKAVG, inrast_TEMPKAVG, row,data_type_tempkavg) < 0) + G_fatal_error (_("Could not read from <%s>"),TEMPKAVG); + if (G_get_raster_row (infd_TEMPKMIN, inrast_TEMPKMIN, row,data_type_tempkmin) < 0) + G_fatal_error (_("Could not read from <%s>"),TEMPKMIN); + if (G_get_raster_row (infd_TEMPKMAX, inrast_TEMPKMAX, row,data_type_tempkmax) < 0) + G_fatal_error (_("Could not read from <%s>"),TEMPKMAX); + if(!original->answer){ + if (G_get_raster_row (infd_P, inrast_P, row,data_type_p) < 0) + G_fatal_error (_("Could not read from <%s>"),P); + } + for (col=0; col < ncols; col++) + { + /* read current cell from line buffer */ + switch(data_type_rnet){ + case CELL_TYPE: + d_rnet = (double) ((CELL *) inrast_RNET)[col]; + break; + case FCELL_TYPE: + d_rnet = (double) ((FCELL *) inrast_RNET)[col]; + break; + case DCELL_TYPE: + d_rnet = ((DCELL *) inrast_RNET)[col]; + break; + } + switch(data_type_tempkavg){ + case CELL_TYPE: + d_tempkavg = (double) ((CELL *) inrast_TEMPKAVG)[col]; + break; + case FCELL_TYPE: + d_tempkavg = (double) ((FCELL *) inrast_TEMPKAVG)[col]; + break; + case DCELL_TYPE: + d_tempkavg = ((DCELL *) inrast_TEMPKAVG)[col]; + break; + } + switch(data_type_tempkmin){ + case CELL_TYPE: + d_tempkmin = (double) ((CELL *) inrast_TEMPKMIN)[col]; + break; + case FCELL_TYPE: + d_tempkmin = (double) ((FCELL *) inrast_TEMPKMIN)[col]; + break; + case DCELL_TYPE: + d_tempkmin = ((DCELL *) inrast_TEMPKMIN)[col]; + break; + } + switch(data_type_tempkmax){ + case CELL_TYPE: + d_tempkmax = (double) ((CELL *) inrast_TEMPKMAX)[col]; + break; + case FCELL_TYPE: + d_tempkmax = (double) ((FCELL *) inrast_TEMPKMAX)[col]; + break; + case DCELL_TYPE: + d_tempkmax = ((DCELL *) inrast_TEMPKMAX)[col]; + break; + } + if(!original->answer){ + switch(data_type_p){ + case CELL_TYPE: + d_p = (double) ((CELL *) inrast_P)[col]; + break; + case FCELL_TYPE: + d_p = (double) ((FCELL *) inrast_P)[col]; + break; + case DCELL_TYPE: + d_p = ((DCELL *) inrast_P)[col]; + break; + } + } + + //Calculate ET + if(original->answer){ + d_daily_et = mh_original( d_rnet, d_tempkavg, d_tempkmax, d_tempkmin, d_p ); + } else { + d_daily_et = mh_eto( d_rnet, d_tempkavg, d_tempkmax, d_tempkmin, d_p ); + } + if (zero->answer && d_daily_et<0) + d_daily_et=0.0; + + /* write calculated ETP to output line buffer */ + ((DCELL *) outrast)[col] = d_daily_et; + } + + if (!flag1->answer) G_percent(row, nrows, 2); + + /* write output line buffer to output raster file */ + if (G_put_raster_row (outfd, outrast, data_type_output) < 0) + G_fatal_error (_("Cannot write to <%s>"), ETa); + + } + /* free buffers and close input maps */ + + G_free(inrast_RNET); + G_free(inrast_TEMPKAVG); + G_free(inrast_TEMPKMIN); + G_free(inrast_TEMPKMAX); + if(!original->answer){ + G_free(inrast_P); + } + G_close_cell (infd_RNET); + G_close_cell (infd_TEMPKAVG); + G_close_cell (infd_TEMPKMIN); + G_close_cell (infd_TEMPKMAX); + if(!original->answer){ + G_close_cell (infd_P); + } + /* generate color table between -20 and 20 */ + G_make_rainbow_colors(&color, -20, 20); + G_write_colors(ETa,G_mapset(),&color); + + G_short_history(ETa,"raster", &history); + G_command_history(&history); + G_write_history(ETa, &history); + + /* free buffers and close output map */ + G_free(outrast); + G_close_cell (outfd); + + return (0); +} + Added: trunk/grassaddons/gipe/r.evapo.MH/mh_eto.c =================================================================== --- trunk/grassaddons/gipe/r.evapo.MH/mh_eto.c (rev 0) +++ trunk/grassaddons/gipe/r.evapo.MH/mh_eto.c 2007-04-06 15:21:52 UTC (rev 453) @@ -0,0 +1,24 @@ +#include +#include +#include + +//Droogers and Allen, 2001. +//p is [mm/month] + +double mh_eto(double ra,double tavg,double tmax,double tmin,double p) +{ + double td, result; + + td = tmax - tmin; + + if (tavg > 100.0){ + tavg=tavg-273.15;//in case temperature is in Kelvin + } + + ra = ra * (84600.0 * 1000.0); // convert W -> MJ/d + + result = 0.0013 * 0.408 * ra * ( tavg + 17.0 ) * pow((td - 0.0123*p),0.76) ; + + return result; +} + Added: trunk/grassaddons/gipe/r.evapo.MH/mh_original.c =================================================================== --- trunk/grassaddons/gipe/r.evapo.MH/mh_original.c (rev 0) +++ trunk/grassaddons/gipe/r.evapo.MH/mh_original.c 2007-04-06 15:21:52 UTC (rev 453) @@ -0,0 +1,23 @@ +#include +#include +#include + +//Hargreaves et al, 1985. + +double mh_original(double ra,double tavg,double tmax,double tmin,double p) +{ + double td, result; + + td = tmax - tmin; + + if (tavg > 100.0){ + tavg=tavg-273.15;// in case Temperature is in Kelvin + } + + ra = ra * (84600.0 * 1000.0); // convert W -> MJ/d + + result = 0.0023 * 0.408 * ra * ( tavg + 17.8 ) * pow(td,0.5) ; + + return result; +} + From cepicky at grass.itc.it Fri Apr 6 19:53:10 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Fri Apr 6 19:53:12 2007 Subject: [grass-addons] r454 - trunk/grassaddons/gui/gui_modules Message-ID: <200704061753.l36HrAS2015086@grass.itc.it> Author: cepicky Date: 2007-04-06 19:53:10 +0200 (Fri, 06 Apr 2007) New Revision: 454 Modified: trunk/grassaddons/gui/gui_modules/dbm.py trunk/grassaddons/gui/gui_modules/sqlbuilder.py Log: database query builder nearly works, better table design Modified: trunk/grassaddons/gui/gui_modules/dbm.py =================================================================== --- trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-06 15:21:52 UTC (rev 453) +++ trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-06 17:53:10 UTC (rev 454) @@ -125,37 +125,8 @@ #setting the numbers of items = number of elements in the dictionary self.itemDataMap = {} self.itemIndexMap = [] - # FIXME: subprocess.Popen should be used - # FIXME: Max. number of rows, while the GUI is still usable - i = 0 - # read data - for line in os.popen("""db.select -c table=%s database=%s driver=%s """ %\ - (self.tablename,self.database,self.driver)): - attributes = line.strip().split("|") - self.itemDataMap[i] = [] - # convert to appropriate type - for j in range(len(attributes)): - try: - attributes[j] = self.columns[j]["type"](attributes[j]) - except: - pass - - # insert to table - index = self.InsertStringItem(sys.maxint, str(attributes[0])) - self.itemDataMap[i].append(attributes[0]) - for j in range(len(attributes[1:])): - self.SetStringItem(index, j+1, str(attributes[j+1])) - self.itemDataMap[i].append(attributes[j+1]) - - self.SetItemData(index, i) - self.itemIndexMap.append(i) - - i += 1 - if i >= 32000: - self.log.write("Can display only 32000 lines") - break - + self.LoadData() #self.SetItemCount(len(self.itemDataMap)) #mixins @@ -187,6 +158,47 @@ # check each 0.1s self.timer.Start(100) + def LoadData(self,where=None): + + cmd = """db.select -c table=%s database=%s driver=%s """ %\ + (self.tablename,self.database,self.driver) + + if where: + self.ClearAll() + cmd = """db.select -c sql="SELECT * FROM %s WHERE %s" database=%s driver=%s """ %\ + (self.tablename,where,self.database,self.driver) + + + # FIXME: subprocess.Popen should be used + # FIXME: Max. number of rows, while the GUI is still usable + i = 0 + # read data + for line in os.popen(cmd): + attributes = line.strip().split("|") + self.itemDataMap[i] = [] + + # convert to appropriate type + for j in range(len(attributes)): + try: + attributes[j] = self.columns[j]["type"](attributes[j]) + except: + pass + + # insert to table + index = self.InsertStringItem(sys.maxint, str(attributes[0])) + self.itemDataMap[i].append(attributes[0]) + for j in range(len(attributes[1:])): + self.SetStringItem(index, j+1, str(attributes[j+1])) + self.itemDataMap[i].append(attributes[j+1]) + + self.SetItemData(index, i) + self.itemIndexMap.append(i) + + i += 1 + if i >= 32000: + self.log.write("Can display only 32000 lines") + break + def OnCloseWindow(self, event): if self.qlayer: self.map.delLayer(item='qlayer') @@ -254,7 +266,7 @@ # FIXME: width=1, because of maybe bug in PNG driver elusion # should be width=3 or something like this - cmd = "d.vect map=%s color=yellow fcolor=yellow cats=%s width=1" % (self.vectmap, catstr) + cmd = "d.vect map=%s color=yellow fcolor=yellow cats=%s width=3" % (self.vectmap, catstr) #print cmd if self.icon: cmd = cmd +" icon=%s" % (self.icon) if self.pointsize: cmd = cmd + " size=%s" % (self.pointsize) @@ -499,15 +511,81 @@ wx.Frame.__init__(self, parent, id, title, size=size, style=style) self.CreateStatusBar(1) + self.vectmap=vectmap log=Log(self) # probably self.gismanager = parent + + # most importand part + self.win = TestVirtualList(self, log,vectmap=vectmap,pointdata=pointdata) - self.win = TestVirtualList(self, log,vectmap=vectmap,pointdata=pointdata) + # buttons + self.btn_apply = wx.Button(self, -1, "Apply") + #self.btn_unselect = wx.Button(self, -1, "Unselect") + self.btn_sqlbuilder = wx.Button(self, -1, "SQL Builder") + + # check + #self.check_add_to_selection = wx.CheckBox(self, -1, "Add to selection") + # textarea + self.text_query = wx.TextCtrl(self,-1,"") + # label + self.sqlabel=wx.StaticText(self,-1,"SELECT * FROM %s WHERE " % self.win.tablename) + self.label_query = wx.StaticText(self,-1,"") + + # box + self.sqlbox = wx.StaticBox(self, -1, "SQL Query:") + + + self.btn_sqlbuilder.Bind(wx.EVT_BUTTON, self.OnBuilder) + + self.__layout() self.Show() + def OnBuilder(self,event): + import sqlbuilder + self.builder = sqlbuilder.SQLFrame(self,-1,"SQL Builder",self.vectmap) + + + def OnApply(self,event): + self.win.LoadData(where=self.text_query.GetValue().strip()) + + + def OnTextEnter(self, event): + pass + + def OnSQLBuilder(self, event): + pass + + def __layout(self): + #self.panel = wx.Panel(self,-1, style=wx.SUNKEN_BORDER) + + self.label_query.SetMinSize((500,50)) + self.text_query.SetMinSize((500,-1)) + + bsizer = wx.StaticBoxSizer(self.sqlbox, wx.VERTICAL) + bsizer.Add(self.label_query, flag=wx.EXPAND) + + + pagesizer= wx.BoxSizer(wx.VERTICAL) + toolsizer1 = wx.BoxSizer(wx.HORIZONTAL) + #toolsizer2 = wx.BoxSizer(wx.HORIZONTAL) + + toolsizer1.Add(self.sqlabel, flag=wx.ALIGN_CENTER_VERTICAL, + proportion=1) + toolsizer1.Add(self.text_query,flag=wx.SHAPED|wx.GROW, proportion=2,) + toolsizer1.Add(self.btn_sqlbuilder,flag=wx.ALIGN_RIGHT,proportion=0) + toolsizer1.Add(self.btn_apply,flag=wx.ALIGN_RIGHT,proportion=0) + + pagesizer.Add(bsizer,flag=wx.EXPAND) + pagesizer.Add(self.win, proportion=1, flag=wx.EXPAND, border=1) + pagesizer.Add(toolsizer1) + + self.SetSizer(pagesizer) + pagesizer.Fit(self) + self.Layout() + def main(argv=None): if argv is None: argv = sys.argv @@ -525,7 +603,7 @@ #wx.InitAllImageHandlers() app = wx.PySimpleApp() - f = AttributeManager(None, -1, "GRASS Attribute Table Manager",wx.Size(500,300),vectmap=argv[1]) + f = AttributeManager(None, -1, "GRASS Attribute Table Manager",wx.Size(700,600),vectmap=argv[1]) app.MainLoop() Modified: trunk/grassaddons/gui/gui_modules/sqlbuilder.py =================================================================== --- trunk/grassaddons/gui/gui_modules/sqlbuilder.py 2007-04-06 15:21:52 UTC (rev 453) +++ trunk/grassaddons/gui/gui_modules/sqlbuilder.py 2007-04-06 17:53:10 UTC (rev 454) @@ -16,7 +16,7 @@ class SQLFrame(wx.Frame): - def __init__(self, parent, id, title, table, qtype="select"): + def __init__(self, parent, id, title, vectmap, qtype="select"): wx.Frame.__init__(self, parent, -1, title) self.SetTitle("SQL Builder for GRASS GIS - %s " % (qtype.upper())) @@ -25,12 +25,25 @@ # # variables # - self.tablename = table # name of the table "select * from #self.tablename# " + self.vectmap = vectmap + if not "@" in self.vectmap: + self.vectmap = self.vectmap+"@"+grassenv.env["MAPSET"] + self.mapname, self.mapset = self.vectmap.split("@") + self.layer,self.tablename, self.column, self.database, self.driver =\ + os.popen("v.db.connect -g map=%s" %\ + (self.vectmap)).readlines()[0].strip().split() + self.qtype = qtype # type of the uqery: SELECT, UPDATE, DELETE, ... self.column_names = [] # array with column names self.columns = {} # array with colum properties self.colvalues = [] # arrya with uniqe values in selected column + self.heading = "" + self.parent = parent + if self.qtype.lower()=="select": + self.heading = "SELECT * FROM %s WHERE" % self.tablename + + # Init self.GetColumns() @@ -40,12 +53,13 @@ # self.btn_clear = wx.Button(self, -1, "Clear") self.btn_verify = wx.Button(self, -1, "Verify") - self.btn_help = wx.Button(self, -1, "Help") - self.btn_load = wx.Button(self, -1, "Load") - self.btn_save = wx.Button(self, -1, "Save") + #self.btn_help = wx.Button(self, -1, "Help") + # self.btn_load = wx.Button(self, -1, "Load") + # self.btn_save = wx.Button(self, -1, "Save") self.btn_apply = wx.Button(self, -1, "Apply") self.btn_close = wx.Button(self, -1, "Close") - self.btn_uniqe = wx.Button(self, -1, "Get unique values") + self.btn_uniqe = wx.Button(self, -1, "Get all values") + self.btn_uniqesample = wx.Button(self, -1, "Get sample") self.btn_is = wx.Button(self, -1, "=") self.btn_isnot = wx.Button(self, -1, "!=") @@ -68,18 +82,19 @@ # # Textareas # - self.text_sql = wx.TextCtrl(self, -1, '', size=(-1,50),style=wx.TE_MULTILINE) + self.text_sql = wx.TextCtrl(self, -1, '', size=(-1,75),style=wx.TE_MULTILINE) # # List Boxes # - self.list_columns = wx.ListBox(self, -1, wx.DefaultPosition, (130, 130), self.column_names, wx.LB_MULTIPLE|wx.LB_SORT) - self.list_values = wx.ListBox(self, -1, wx.DefaultPosition, (130, 130), self.colvalues, wx.LB_MULTIPLE|wx.LB_SORT) + self.list_columns = wx.ListBox(self, -1, wx.DefaultPosition, (-1, -1), self.column_names, wx.LB_MULTIPLE|wx.LB_SORT) + self.list_values = wx.ListBox(self, -1, wx.DefaultPosition, (-1, -1), self.colvalues, wx.LB_MULTIPLE|wx.LB_SORT) # # Bindings # self.btn_uniqe.Bind(wx.EVT_BUTTON, self.GetUniqueValues) + self.btn_uniqesample.Bind(wx.EVT_BUTTON, self.GetSampleValues) self.btn_is.Bind(wx.EVT_BUTTON, self.AddMark) self.btn_isnot.Bind(wx.EVT_BUTTON, self.AddMark) self.btn_like.Bind(wx.EVT_BUTTON, self.AddMark) @@ -92,22 +107,38 @@ self.btn_brackets.Bind(wx.EVT_BUTTON, self.AddMark) self.btn_prc.Bind(wx.EVT_BUTTON, self.AddMark) self.btn_and.Bind(wx.EVT_BUTTON, self.AddMark) + self.btn_close.Bind(wx.EVT_BUTTON, self.OnClose) + self.btn_clear.Bind(wx.EVT_BUTTON, self.OnClear) + self.btn_verify.Bind(wx.EVT_BUTTON, self.OnVerify) + self.btn_apply.Bind(wx.EVT_BUTTON, self.OnApply) self.list_columns.Bind(wx.EVT_LISTBOX, self.AddColumnName) self.list_values.Bind(wx.EVT_LISTBOX, self.AddValue) + + self.__doLayout() - # - # Layout - # + def __doLayout(self): + databasebox = wx.StaticBox(self, -1, "Database connection") + databaseboxsizer = wx.StaticBoxSizer(databasebox,wx.VERTICAL) + dbstr = "Database: %s\n" % (self.database) + dbstr += "Driver: %s\n" % (self.driver) + dbstr += "Table: %s" % (self.tablename) + databaseboxsizer.Add(wx.StaticText(self,-1,dbstr), flag=wx.EXPAND) + + sqlbox = wx.StaticBox(self, -1, "%s" % self.heading) + sqlboxsizer = wx.StaticBoxSizer(sqlbox,wx.VERTICAL) + sqlboxsizer.Add(self.text_sql, flag=wx.EXPAND) + + pagesizer = wx.BoxSizer(wx.VERTICAL) - buttonsizer1 = wx.BoxSizer(wx.HORIZONTAL) - buttonsizer1.Add(self.btn_clear, 0, wx.LEFT|wx.RIGHT, 5) - buttonsizer1.Add(self.btn_verify, 0, wx.LEFT|wx.RIGHT, 5 ) - buttonsizer1.Add(self.btn_help, 0, wx.LEFT|wx.RIGHT, 5, ) - buttonsizer1.Add(self.btn_load, 0, wx.LEFT|wx.RIGHT, 5,) - buttonsizer1.Add(self.btn_save, 0, wx.LEFT|wx.RIGHT, 5, ) - buttonsizer1.Add(self.btn_apply, 0, wx.LEFT|wx.RIGHT, 5, ) + buttonsizer1 = wx.GridBagSizer(2,2) + buttonsizer1.Add(self.btn_clear, (0,0)) + buttonsizer1.Add(self.btn_verify, (0,1)) + #buttonsizer1.Add(self.btn_help, (0,2)) + #buttonsizer1.Add(self.btn_load, (0,2)) + # buttonsizer1.Add(self.btn_save, (0,3)) + buttonsizer1.Add(self.btn_apply, (0,2)) buttonsizer2 = wx.GridBagSizer(2, 2) buttonsizer2.Add(self.btn_is, (0,0)) @@ -130,18 +161,33 @@ buttonsizer3.Add(self.btn_apply,0,wx.RIGHT,5) buttonsizer3.Add(self.btn_close,0,wx.RIGHT,5) - hsizer1 = wx.GridSizer(2, 2, 0, 0) - hsizer1.Add((wx.StaticText(self,-1,"Columns: ",size=(-1,22))), proportion=0,border=0) - hsizer1.Add((wx.StaticText(self,-1,"Unique values: ", size=(-1,22))), proportion=0,border=0) - hsizer1.Add(self.list_columns, 1, wx.EXPAND) - hsizer1.Add(self.list_values, 1, wx.EXPAND) + buttonsizer4 = wx.BoxSizer(wx.HORIZONTAL) + buttonsizer4.Add(self.btn_uniqesample,0,flag=wx.ALIGN_CENTER_HORIZONTAL,border=5) + buttonsizer4.Add(self.btn_uniqe,0,flag=wx.ALIGN_CENTER_HORIZONTAL,border=5) - pagesizer.Add((wx.StaticText(self,-1,self.qtype,size=(-1,22))), 0, 0) - pagesizer.Add(hsizer1, 1, wx.EXPAND, 0) - pagesizer.Add(self.btn_uniqe,0,wx.ALIGN_LEFT|wx.TOP,border=5) + hsizer1 = wx.BoxSizer(wx.HORIZONTAL) + #hsizer2 = wx.BoxSizer(wx.HORIZONTAL) + + columnsbox = wx.StaticBox(self,-1,"Columns: ") + valuesbox = wx.StaticBox(self,-1,"Values: ") + #hsizer1.Add(wx.StaticText(self,-1, "Unique values: "), border=0, proportion=1) + columnsizer = wx.StaticBoxSizer(columnsbox,wx.VERTICAL) + valuesizer = wx.StaticBoxSizer(valuesbox,wx.VERTICAL) + columnsizer.Add(self.list_columns, flag=wx.EXPAND,) + valuesizer.Add(self.list_values, flag=wx.EXPAND) + self.list_columns.SetMinSize((-1,130)) + self.list_values.SetMinSize((-1,100)) + valuesizer.Add(buttonsizer4) + hsizer1.Add(columnsizer,border=0, proportion=1) + hsizer1.Add(valuesizer,border=0, proportion=1) + + pagesizer.Add(databaseboxsizer,flag=wx.EXPAND,border=5) + pagesizer.Add(hsizer1, 1,flag=wx.EXPAND,border=5) + #pagesizer.Add(self.btn_uniqe,0,wx.ALIGN_LEFT|wx.TOP,border=5) + #pagesizer.Add(self.btn_uniqesample,0,wx.ALIGN_LEFT|wx.TOP,border=5) pagesizer.Add(buttonsizer2, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.TOP, border=5) - pagesizer.Add(self.text_sql, proportion=1, flag=wx.EXPAND|wx.TOP, border=5) - pagesizer.Add(buttonsizer1, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.TOP, 5) + pagesizer.Add(sqlboxsizer, flag=wx.EXPAND,border=5) + pagesizer.Add(buttonsizer1, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.TOP, border=5) pagesizer.Add(buttonsizer3, proportion=0, flag=wx.TOP, border=5) self.SetAutoLayout(True) self.SetSizer(pagesizer) @@ -158,7 +204,7 @@ self.columns[name] = {'type':ctype} return - def GetUniqueValues(self,event): + def GetUniqueValues(self,event,justsample=False): vals = [] try: idx = self.list_columns.GetSelections()[0] @@ -166,9 +212,18 @@ return self.list_values.Clear() column = self.list_columns.GetString(idx) - for line in os.popen("""db.select -c sql="SELECT %s FROM %s" """ %\ - (column,self.tablename)): - self.list_values.Insert(line.strip(),0) + i = 0 + for line in os.popen("""db.select -c database=%s driver=%s sql="SELECT %s FROM %s" """ %\ + (self.database,self.driver,column,self.tablename)): + if justsample and i < 256 or \ + not justsample: + self.list_values.Insert(line.strip(),0) + else: + break + i += 1 + + def GetSampleValues(self,event): + self.GetUniqueValues(None,True) def AddColumnName(self,event): idx = self.list_columns.GetSelections()[0] @@ -206,7 +261,7 @@ def __addSomething(self,what): sqlstr = self.text_sql.GetValue() newsqlstr = '' - position = self.text_sql.GetLastPosition() + position = self.text_sql.GetPosition()[0] selection = self.text_sql.GetSelection() newsqlstr = sqlstr[:position] @@ -221,7 +276,25 @@ self.text_sql.SetValue(newsqlstr) self.text_sql.SetInsertionPoint(position) + def OnApply(self,event): + if self.parent: + try: + self.parent.text_query.SetValue= self.text_sql.GetValue().strip().replace("\n"," ") + except: + pass + def OnVerify(self,event): + if self.text_sql.GetValue(): + if os.system("""db.select -t driver=%s database=%s sql="SELECT * FROM %s WHERE %s" """ % \ + (self.driver, self.database,self.tablename, + self.text_sql.GetValue().strip().replace("\n"," "))): + # FIXME: LOG + print self.text_sql.GetValue().strip().replace("\n"," "), "not correct!" + def OnClear(self, event): + self.text_sql.SetValue("") + def OnClose(self,event): + self.Destroy() + if __name__ == "__main__": if len(sys.argv) != 2: @@ -229,7 +302,7 @@ sys.exit() app = wx.App(0) - sqlb = SQLFrame(None, -1, 'SQL Buiilder',sys.argv[1]) + sqlb = SQLFrame(None, -1, 'SQL Builder',sys.argv[1]) app.MainLoop() From calvelo at grass.itc.it Sun Apr 8 00:21:39 2007 From: calvelo at grass.itc.it (calvelo@grass.itc.it) Date: Sun Apr 8 00:21:40 2007 Subject: [grass-addons] r455 - trunk/grassaddons/gui/gui_modules Message-ID: <200704072221.l37MLdCp015550@grass.itc.it> Author: calvelo Date: 2007-04-08 00:21:29 +0200 (Sun, 08 Apr 2007) New Revision: 455 Modified: trunk/grassaddons/gui/gui_modules/menuform.py Log: Fixed bugs in - sys.path setting, - pre-filled parameter handling from wxgui and - SO-dependent line-breaks Modified: trunk/grassaddons/gui/gui_modules/menuform.py =================================================================== --- trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-06 17:53:10 UTC (rev 454) +++ trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-07 22:21:29 UTC (rev 455) @@ -52,10 +52,10 @@ import os from os import system +sys.path.append(os.path.join(os.getenv("GISBASE"),"etc","wx")) try: import subprocess except: - sys.path.append(os.path.join(os.getenv("GISBASE"),"etc","wx")) from compat import subprocess import re @@ -122,7 +122,7 @@ "Make really long texts shorter" # TODO: remove magic number (calculate a correct value from # pixelSize of text and the magic number for maximum size - return escape_ampersand( "\n".join( textwrap.wrap( normalize_whitespace(someString), 72 ) ) ) + return escape_ampersand( os.linesep.join( textwrap.wrap( normalize_whitespace(someString), 72 ) ) ) def escape_ampersand(text): "Escapes ampersands with additional ampersand for GUI" @@ -853,8 +853,9 @@ xml.sax.parseString( getInterfaceDescription( cmd ) , handler ) # if layer parameters previously set, re-insert them into dialog - if 'params' in dcmd_params: self.grass_task.params = dcmd_params['params'] - if 'flags' in dcmd_params: self.grass_task.flags = dcmd_params['flags'] + if completed is not None: + if 'params' in dcmd_params: self.grass_task.params = dcmd_params['params'] + if 'flags' in dcmd_params: self.grass_task.flags = dcmd_params['flags'] self.mf = mainFrame(self.parent ,-1, self.grass_task, get_dcmd, layer) self.mf.Show(True) From barton at grass.itc.it Sun Apr 8 06:56:47 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Sun Apr 8 06:56:48 2007 Subject: [grass-addons] r456 - trunk/grassaddons/gui/gui_modules Message-ID: <200704080456.l384ul1L013521@grass.itc.it> Author: barton Date: 2007-04-08 06:56:36 +0200 (Sun, 08 Apr 2007) New Revision: 456 Modified: trunk/grassaddons/gui/gui_modules/dbm.py trunk/grassaddons/gui/gui_modules/sqlbuilder.py Log: Fixed bug in parsing of db.describe output Modified: trunk/grassaddons/gui/gui_modules/dbm.py =================================================================== --- trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-07 22:21:29 UTC (rev 455) +++ trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-08 04:56:36 UTC (rev 456) @@ -103,7 +103,7 @@ for line in os.popen("db.describe -c table=%s driver=%s database=%s" %\ (self.tablename, self.driver, self.database)).readlines()[1:]: - x,column,type = line.strip().split(":") + x,column,type,length = line.strip().split(":") # FIXME: here will be more types if type.lower().find("integer") > -1: self.columns.append({"name":column,"type":int}) @@ -159,7 +159,7 @@ self.timer.Start(100) def LoadData(self,where=None): - + cmd = """db.select -c table=%s database=%s driver=%s """ %\ (self.tablename,self.database,self.driver) @@ -517,7 +517,7 @@ # probably self.gismanager = parent - + # most importand part self.win = TestVirtualList(self, log,vectmap=vectmap,pointdata=pointdata) @@ -525,7 +525,7 @@ self.btn_apply = wx.Button(self, -1, "Apply") #self.btn_unselect = wx.Button(self, -1, "Unselect") self.btn_sqlbuilder = wx.Button(self, -1, "SQL Builder") - + # check #self.check_add_to_selection = wx.CheckBox(self, -1, "Add to selection") # textarea @@ -533,7 +533,7 @@ # label self.sqlabel=wx.StaticText(self,-1,"SELECT * FROM %s WHERE " % self.win.tablename) self.label_query = wx.StaticText(self,-1,"") - + # box self.sqlbox = wx.StaticBox(self, -1, "SQL Query:") Modified: trunk/grassaddons/gui/gui_modules/sqlbuilder.py =================================================================== --- trunk/grassaddons/gui/gui_modules/sqlbuilder.py 2007-04-07 22:21:29 UTC (rev 455) +++ trunk/grassaddons/gui/gui_modules/sqlbuilder.py 2007-04-08 04:56:36 UTC (rev 456) @@ -22,7 +22,7 @@ self.SetTitle("SQL Builder for GRASS GIS - %s " % (qtype.upper())) self.SetIcon(wx.Icon(os.path.join(imagepath,'grass_sql.png'), wx.BITMAP_TYPE_ANY)) - # + # # variables # self.vectmap = vectmap @@ -73,26 +73,26 @@ self.btn_and = wx.Button(self, -1, "AND") self.btn_brackets = wx.Button(self, -1, "()") self.btn_prc = wx.Button(self, -1, "%") - - # + + # # Text labels # #self.label_headding = wx.StaticText(self, -1, '') # # Textareas - # + # self.text_sql = wx.TextCtrl(self, -1, '', size=(-1,75),style=wx.TE_MULTILINE) - # + # # List Boxes # self.list_columns = wx.ListBox(self, -1, wx.DefaultPosition, (-1, -1), self.column_names, wx.LB_MULTIPLE|wx.LB_SORT) self.list_values = wx.ListBox(self, -1, wx.DefaultPosition, (-1, -1), self.colvalues, wx.LB_MULTIPLE|wx.LB_SORT) - + # # Bindings - # + # self.btn_uniqe.Bind(wx.EVT_BUTTON, self.GetUniqueValues) self.btn_uniqesample.Bind(wx.EVT_BUTTON, self.GetSampleValues) self.btn_is.Bind(wx.EVT_BUTTON, self.AddMark) @@ -114,7 +114,7 @@ self.list_columns.Bind(wx.EVT_LISTBOX, self.AddColumnName) self.list_values.Bind(wx.EVT_LISTBOX, self.AddValue) - + self.__doLayout() def __doLayout(self): @@ -167,7 +167,7 @@ hsizer1 = wx.BoxSizer(wx.HORIZONTAL) #hsizer2 = wx.BoxSizer(wx.HORIZONTAL) - + columnsbox = wx.StaticBox(self,-1,"Columns: ") valuesbox = wx.StaticBox(self,-1,"Values: ") #hsizer1.Add(wx.StaticText(self,-1, "Unique values: "), border=0, proportion=1) @@ -200,7 +200,7 @@ for line in os.popen("db.columns table=%s" % (self.tablename)): self.column_names.append(line.strip()) for line in os.popen("db.describe -c table=%s" % (self.tablename)).readlines()[1:]: - x,name,ctype = line.strip().split(":") + x,name,ctype,length = line.strip().split(":") self.columns[name] = {'type':ctype} return @@ -221,7 +221,7 @@ else: break i += 1 - + def GetSampleValues(self,event): self.GetUniqueValues(None,True) @@ -257,7 +257,7 @@ elif event.GetId() == self.btn_prc.GetId(): mark = "%" self.__addSomething(mark) - + def __addSomething(self,what): sqlstr = self.text_sql.GetValue() newsqlstr = '' From barton at grass.itc.it Sun Apr 8 18:43:35 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Sun Apr 8 18:43:36 2007 Subject: [grass-addons] r457 - trunk/grassaddons/gui/gui_modules Message-ID: <200704081643.l38GhZUK022747@grass.itc.it> Author: barton Date: 2007-04-08 18:43:26 +0200 (Sun, 08 Apr 2007) New Revision: 457 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py Log: Fixed overlay transparency. Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-08 04:56:36 UTC (rev 456) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-08 16:43:26 UTC (rev 457) @@ -158,11 +158,11 @@ def __init__(self, parent, id, pos = wx.DefaultPosition, size = wx.DefaultSize, - style=wx.NO_FULL_REPAINT_ON_RESIZE,map=Map): + style=wx.NO_FULL_REPAINT_ON_RESIZE): wx.Window.__init__(self, parent, id, pos, size, style) self.parent = parent - self.Map = map + self.Map = Map # # Flags @@ -275,15 +275,18 @@ drawid == None else: drawid = wx.NewId() - - if drawid: + else: self.ovlcoords[drawid] = coords self.ovlchk[drawid] = True pdc.SetId(drawid) self.select[drawid] = False pdc.BeginDrawing() - pdc.SetBackground(wx.Brush(self.GetBackgroundColour())) + if drawid != 99: + bg = wx.TRANSPARENT_BRUSH + else: + bg = wx.Brush(self.GetBackgroundColour()) + pdc.SetBackground(bg) pdc.Clear() self.Refresh() @@ -292,6 +295,7 @@ return if pdctype == 'image': + mask = None bitmap = wx.BitmapFromImage(img) w,h = bitmap.GetSize() pdc.DrawBitmap(bitmap, coords[0], coords[1], True) # draw the composite map @@ -315,17 +319,21 @@ pen = self.RandomPen() pdc.SetPen(pen) pdc.DrawPoint(coords[0], coords[1]) + coords[0] = coords[0] - 5 + coords[1] = coords[1] - 5 + coords[2] = coords[0] + 5 + coords[3] = coords[1] + 5 pdc.SetIdBounds(drawid,(coords[0], coords[1], coords[2], coords[3])) self.ovlcoords[drawid] = coords elif pdctype == 'text': # draw text on top of map - print 'in draw: font info, id, pdctype = ',img,drawid,pdctype + text = img[0] - w,h = self.GetFullTextExtent(text)[0:2] + w,h = self.GetFullTextExtent(img[0])[0:2] + coords[2], coords[3] = coords[0] + w, coords[1] + h pdc.SetFont(img[1]) pdc.SetTextForeground(img[2]) -# pdc.SetTextBackground(self.RandomColor()) - pdc.DrawText(text, coords[0], coords[1]) + pdc.DrawText(img[0], coords[0], coords[1]) pdc.SetIdBounds(drawid, (coords[0], coords[1], coords[2], coords[3])) self.ovlcoords[drawid] = coords @@ -341,7 +349,6 @@ # use PrepateDC to set position correctly self.PrepareDC(dc) # we need to clear the dc BEFORE calling PrepareDC -# bg = wx.TRANSPARENT_BRUSH bg = wx.Brush(self.GetBackgroundColour()) dc.SetBackground(bg) dc.Clear() @@ -408,6 +415,7 @@ for ovlfile in self.Map.ovlist: if os.path.isfile(ovlfile) and os.path.getsize(ovlfile): img = wx.Image(ovlfile, wx.BITMAP_TYPE_ANY) +# img.ConvertAlphaToMask() ovlist.append(img) self.imagedict[img] = ovlist.index(img) # set image PeudoDC ID return ovlist @@ -890,7 +898,7 @@ # self.InitDisplay() # initialize region values # self.MapWindow = DrawWindow(self) # initialize buffered DC - self.MapWindow = BufferedWindow(self, id = wx.ID_ANY,map=self.Map) # initialize buffered DC + self.MapWindow = BufferedWindow(self, id = wx.ID_ANY) # initialize buffered DC self.MapWindow.Bind(wx.EVT_MOTION, self.OnMotion) # decoration overlays @@ -1133,7 +1141,6 @@ """ Query currrent or last map """ - print "Quering" self.MapWindow.mouse['box'] = "query" self.MapWindow.zoomtype = 0 #event.Skip() @@ -1283,9 +1290,11 @@ Handler for text decoration menu selection. """ ovltype = 2 # index for overlay layer in render + maptext = '' + textfont = self.GetFont() + textcolor = wx.BLACK id = wx.NewId()+100 - print 'in addText' dlg = TextDialog(self, wx.ID_ANY, 'Text', size=(350, 200), style=wx.DEFAULT_DIALOG_STYLE, @@ -1297,11 +1306,9 @@ # If OK button pressed in decoration control dialog val = dlg.ShowModal() if val == wx.ID_OK: - print 'OK' maptext = dlg.currText textfont = dlg.currFont textcolor = dlg.currClr - print 'text, font, color =', maptext,textfont,textcolor self.MapWindow.Draw(self.MapWindow.pdc, img=(maptext,textfont,textcolor), drawid=id, pdctype='text') @@ -1400,7 +1407,6 @@ """ Controls setting options and displaying/hiding map overlay decorations """ - print 'in textdialog' self.ovltype = ovltype self.drawid = drawid @@ -1481,10 +1487,6 @@ ptsize = font.GetPointSize() colour = data.GetColour() - print ('You selected: "%s", %d points, color %s\n' % - (font.GetFaceName(), font.GetPointSize(), - colour.Get())) - self.currFont = font self.currClr = colour self.UpdateUI() From barton at grass.itc.it Mon Apr 9 00:19:10 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Mon Apr 9 00:19:12 2007 Subject: [grass-addons] r458 - trunk/grassaddons/gui/gui_modules Message-ID: <200704082219.l38MJAl4027125@grass.itc.it> Author: barton Date: 2007-04-09 00:19:02 +0200 (Mon, 09 Apr 2007) New Revision: 458 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py Log: Overlay text partly working Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-08 16:43:26 UTC (rev 457) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-08 22:19:02 UTC (rev 458) @@ -191,6 +191,8 @@ self.imagedict = {} # images and their ID's for painting and dragging self.select = {} # selecting/unselecting decorations for dragging self.ovlchk = {} # showing/hiding decorations + self.textdict = {} # text, font, and color indexed by id + self.currtxtid = None # PseudoDC id for currently selected text # # mouse attributes like currently pressed buttons, position on @@ -222,47 +224,6 @@ self.dragid = -1 self.lastpos = (0,0) - -# def Draw(self, dc, img=None, pdctype='image', coords='0,0'): -# """ -# Just here as a place holder. -# This method should be over-ridden when sub-classed -# """ -# pass -# -# def DrawOvl(self, pdc, type, data, pdctype='image', coords=wx.Rect(0,0,0,0)): -# """ -# Draws map decorations on top of map -# Just here as a place holder. -# This method should be over-ridden when sub-classed -# """ -# pass - -# def Draw(self, dc, img=None, pdctype='image', coords=[0, 0]): -# """ -# Draws image, box and line in the background -# """ -# dc.BeginDrawing() -# dc.SetBackground(wx.Brush(self.GetBackgroundColour())) -# dc.Clear() # make sure you clear the bitmap! -# -# if dctype == 'clear': # erase the display -# dc.EndDrawing() -# return -# bitmap = wx.BitmapFromImage(img) -# dc.DrawBitmap(bitmap, 0, 0, True) # draw the composite map -# -# if dctype == 'box': # draw a box on top of the map -# dc.SetBrush(wx.Brush(wx.CYAN, wx.TRANSPARENT)) -# dc.SetPen(self.pen) -# dc.DrawRectangle(coords[0], coords[1], coords[2], coords[3]) -# elif dctype == 'line': # draw a line on top of the map -# dc.SetBrush(wx.Brush(wx.CYAN, wx.TRANSPARENT)) -# dc.SetPen(self.pen) -# dc.DrawLine(coords[0], coords[1], coords[2], coords[3]) -# -# dc.EndDrawing() - def Draw(self, pdc, img=None, drawid=None, pdctype='image', coords=[0,0,0,0]): """ Draws map decorations on top of map @@ -466,7 +427,7 @@ self.pdc.RemoveAll() self.Draw(self.pdc, self.img, drawid=id) # draw map image background self.ovlist = self.GetOverlay() # list of decoration overlay images - if self.ovlist != []: + if self.ovlist != []: # draw scale and legend overlays for img in self.ovlist: id = self.imagedict[img] if id not in self.ovlcoords: self.ovlcoords[id] = wx.Rect(0,0,0,0) @@ -474,8 +435,13 @@ if id not in self.ovlchk: self.ovlchk[id] = False if self.ovlchk[id] == True: # draw any active and defined overlays self.Draw(self.pdc, img=img, drawid=id, - pdctype='image', coords=self.ovlcoords[id]) + pdctype='image', coords=self.ovlcoords[id]) + if self.textdict != None: # draw text overlays + for id in self.textdict: + self.Draw(self.pdc, img=self.textdict[id], drawid=id, + pdctype='text', coords=self.ovlcoords[id]) + self.resize = False # update statusbar @@ -571,9 +537,11 @@ self.select[id] = not self.select[id] if self.select[id] == True: self.dragid = id + if id > 100: self.currtxtid = id else: self.ovlcoords[self.dragid] = self.pdc.GetIdBounds(self.dragid) self.dragid = None + self.currtxtid = None self.UpdateMap() id = None @@ -719,107 +687,6 @@ self.Map.region['e'] = newreg['e'] self.Map.region['w'] = newreg['w'] - -#class DrawWindow(BufferedWindow): -# """ -# Drawing routine for double buffered drawing. Overwrites Draw method -# in the BufferedWindow class -# """ -# def __init__(self, parent, id = wx.ID_ANY): -# """ -# """ -# ## Any data the Draw() function needs must be initialized before -# ## calling BufferedWindow.__init__, as it will call the Draw -# ## function. -# self.dcmd_list = [] # list of display commands to process -# BufferedWindow.__init__(self, parent, id) -# -# def Draw(self, dc, img=None, pdctype='image', coords=[0, 0]): -# """ -# Draws image, box and line in the background -# """ -# dc.BeginDrawing() -# dc.SetBackground(wx.Brush(self.GetBackgroundColour())) -# dc.Clear() # make sure you clear the bitmap! -# -# if dctype == 'clear': # erase the display -# dc.EndDrawing() -# return -# bitmap = wx.BitmapFromImage(img) -# dc.DrawBitmap(bitmap, 0, 0, True) # draw the composite map -# -# if dctype == 'box': # draw a box on top of the map -# dc.SetBrush(wx.Brush(wx.CYAN, wx.TRANSPARENT)) -# dc.SetPen(self.pen) -# dc.DrawRectangle(coords[0], coords[1], coords[2], coords[3]) -# elif dctype == 'line': # draw a line on top of the map -# dc.SetBrush(wx.Brush(wx.CYAN, wx.TRANSPARENT)) -# dc.SetPen(self.pen) -# dc.DrawLine(coords[0], coords[1], coords[2], coords[3]) -# -# dc.EndDrawing() -# -# def DrawOvl(self, pdc, img=None, id=None, pdctype='image', coords=[0,0,0,0]): -# """ -# Draws map decorations on top of map -# """ -# pdc.BeginDrawing() -# -# if id == None: -# if pdctype == 'image' : -# id = imagedict[img] -# else: -# id = wx.NewId() -# pdc.SetId(id) -# self.select[id] = False -# -# if pdctype == 'clear': # erase the display -## if type > -1: -## pdc.RemoveId(id) -# pdc.EndDrawing() -# return -# -# elif pdctype == 'image': -# bitmap = wx.BitmapFromImage(img) -# w,h = bitmap.GetSize() -# pdc.DrawBitmap(bitmap, coords[0], coords[1], True) # draw the composite map -# pdc.SetIdBounds(id, (coords[0],coords[1],w,h)) -# -# elif pdctype == 'box': # draw a box on top of the map -# pdc.SetBrush(wx.Brush(wx.CYAN, wx.TRANSPARENT)) -# pdc.SetPen(self.pen) -# pdc.DrawRectangle(coords[0], coords[1], coords[2], coords[3]) -# rect.Inflate(pen.GetWidth(),pen.GetWidth()) -# pdc.SetIdBounds(id,(coords[0], coords[1], coords[2], coords[3])) -# -# elif pdctype == 'line': # draw a line on top of the map -# pdc.SetBrush(wx.Brush(wx.CYAN, wx.TRANSPARENT)) -# pdc.SetPen(self.pen) -# dc.DrawLine(rect) -# rect.Inflate(pen.GetWidth(),pen.GetWidth()) -# pdc.SetIdBounds(id,(coords[0], coords[1], coords[2], coords[3])) -# -# elif pdctype == 'point': #draw point -# pen = self.RandomPen() -# pdc.SetPen(pen) -# pdc.DrawPoint(coords[0], coords[1]) -# rect.Inflate(pen.GetWidth(),pen.GetWidth()) -# pdc.SetIdBounds(id,(coords[0], coords[1], coords[2], coords[3])) -# -# elif pdctype == 'text': # draw text on top of map -# text = img -# w,h = self.GetFullTextExtent(text)[0:2] -# pdc.SetFont(self.GetFont()) -# pdc.SetTextForeground(self.RandomColor()) -# pdc.SetTextBackground(self.RandomColor()) -# pdc.DrawText(text, coords[0], coords[1]) -# rect.Inflate(2,2) -# pdc.SetIdBounds(id, (coords[0], coords[1], coords[2], coords[3])) -# -# pdc.EndDrawing() -# self.Refresh() - - class MapFrame(wx.Frame): """ Main frame for map display window. Drawing takes place in child double buffered @@ -844,6 +711,8 @@ style -- window style toolbars-- array of default toolbars, which should appear map, digit + cb -- control book ID in GIS Manager + idx -- index of display """ wx.Frame.__init__(self, parent, id, title, pos, size, style) @@ -905,6 +774,7 @@ self.ovlchk = self.MapWindow.ovlchk self.ovlcoords = self.MapWindow.ovlcoords self.params = {} # previously set decoration options parameters to insert into options dialog + # # Bind various events # ONLY if we are running from GIS manager @@ -1293,10 +1163,15 @@ maptext = '' textfont = self.GetFont() textcolor = wx.BLACK + textcoords = [0,0,0,0] - id = wx.NewId()+100 + if self.MapWindow.currtxtid == None: # text doesn't already exist + id = wx.NewId()+100 + else: # text already exists + id = self.MapWindow.currtxtid + textcoords=self.ovlcoords[id] - dlg = TextDialog(self, wx.ID_ANY, 'Text', size=(350, 200), + dlg = TextDialog(self, wx.ID_ANY, 'Text', size=(400, 200), style=wx.DEFAULT_DIALOG_STYLE, ovltype=ovltype, drawid=id) @@ -1310,10 +1185,12 @@ textfont = dlg.currFont textcolor = dlg.currClr - self.MapWindow.Draw(self.MapWindow.pdc, img=(maptext,textfont,textcolor), drawid=id, pdctype='text') + self.MapWindow.textdict[id] = (maptext,textfont,textcolor) + self.MapWindow.Draw(self.MapWindow.pdc, img=self.MapWindow.textdict[id], + drawid=id, pdctype='text', coords=textcoords) + self.MapWindow.Update() - def getOptData(self, dcmd, type, params): """ Callback method for decoration overlay command generated by @@ -1400,9 +1277,7 @@ class TextDialog(wx.Dialog): def __init__(self, parent, id, title, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_DIALOG_STYLE, - ovltype=2,drawid=None,currText='', - currClr=wx.BLACK, - currFont=''): + ovltype=2,drawid=None): wx.Dialog.__init__(self, parent, id, title, pos, size, style) """ Controls setting options and displaying/hiding map overlay decorations @@ -1410,13 +1285,14 @@ self.ovltype = ovltype self.drawid = drawid - self.currClr = currClr - self.currText = currText - self.currFont = currFont -# self.ovlcmd = cmd -# self.ovlchk = self.Parent.MapWindow.ovlchk -# self.params = params #previously set decoration options to pass back to options dialog + if drawid in self.Parent.MapWindow.textdict: + self.currText,self.currFont,self.currClr = self.Parent.MapWindow.textdict[drawid] + else: + self.currClr = wx.BLACK + self.currText = '' + self.currFont = self.GetFont() + sizer = wx.BoxSizer(wx.VERTICAL) box = wx.BoxSizer(wx.HORIZONTAL) @@ -1424,8 +1300,10 @@ label = wx.StaticText(self, -1, "Enter text:") box.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5) - self.textentry = wx.TextCtrl(self, -1, "", size=(80,-1)) - self.currFont = self.textentry.GetFont() + self.textentry = wx.TextCtrl(self, -1, "", size=(200,-1)) + self.textentry.SetFont(self.currFont) + self.textentry.SetForegroundColour(self.currClr) + self.textentry.SetValue(self.currText) box.Add(self.textentry, 0, wx.ALIGN_CENTRE|wx.ALL, 5) sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) @@ -1435,7 +1313,10 @@ sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) box = wx.BoxSizer(wx.HORIZONTAL) - label = wx.StaticText(self, -1, ("Double-click text with mouse in\npointer mode and drag to position.\nDouble-click again to set")) + label = wx.StaticText(self, -1, ("Double-click text with mouse in\ + \npointer mode and drag to position.\ + \nEdit by selecting text from overlay menu.\ + \nDouble-click again to set")) box.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5) sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) @@ -1482,31 +1363,16 @@ if dlg.ShowModal() == wx.ID_OK: data = dlg.GetFontData() - font = data.GetChosenFont() - face = font.GetFaceName() - ptsize = font.GetPointSize() - colour = data.GetColour() + self.currFont = data.GetChosenFont() + self.currClr = data.GetColour() - self.currFont = font - self.currClr = colour - self.UpdateUI() + self.textentry.SetFont(self.currFont) + self.textentry.SetForegroundColour(self.currClr) - # Don't destroy the dialog until you get everything you need from the - # dialog! + self.Layout() + dlg.Destroy() - def UpdateUI(self): - self.textentry.SetFont(self.currFont) - self.textentry.SetForegroundColour(self.currClr) -# self.ps.SetLabel(str(self.currFont.GetPointSize())) -# self.family.SetLabel(self.currFont.GetFamilyString()) -# self.style.SetLabel(self.currFont.GetStyleString()) -# self.weight.SetLabel(self.currFont.GetWeightString()) -# self.face.SetLabel(self.currFont.GetFaceName()) -# self.nfi.SetLabel(self.currFont.GetNativeFontInfo().ToString()) - self.Layout() - - class MapApp(wx.App): """ MapApp class From barton at grass.itc.it Mon Apr 9 08:24:34 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Mon Apr 9 08:24:36 2007 Subject: [grass-addons] r459 - trunk/grassaddons/gui/gui_modules Message-ID: <200704090624.l396OYC6031705@grass.itc.it> Author: barton Date: 2007-04-09 08:24:25 +0200 (Mon, 09 Apr 2007) New Revision: 459 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py Log: Multiple text overlays work now. Scale and legend overlays improved. Double click to change options. Drag with mouse to reposition. Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-08 22:19:02 UTC (rev 458) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-09 06:24:25 UTC (rev 459) @@ -186,9 +186,10 @@ # self.mapfile = None # image file to be rendered self.img = "" # wx.Image object (self.mapfile) - self.ovlist = [] # list of images for overlays + self.ovldict = {} # list of images for overlays self.ovlcoords = {} # positioning coordinates for decoration overlay - self.imagedict = {} # images and their ID's for painting and dragging + self.imagedict = {} # images and their PseudoDC ID's for painting and dragging + self.crop = {} # coordinates to crop overlays to their data, indexed by image ID self.select = {} # selecting/unselecting decorations for dragging self.ovlchk = {} # showing/hiding decorations self.textdict = {} # text, font, and color indexed by id @@ -258,6 +259,9 @@ if pdctype == 'image': mask = None bitmap = wx.BitmapFromImage(img) +# if drawid in self.ovldict: +# w,h = self.ovldict[drawid][1] +# else: w,h = bitmap.GetSize() pdc.DrawBitmap(bitmap, coords[0], coords[1], True) # draw the composite map pdc.SetIdBounds(drawid, (coords[0],coords[1],w,h)) @@ -371,17 +375,61 @@ """ Converts overlay files to wx.Image """ - ovlist = [] + self.ovldict = {} if self.Map.ovlist: for ovlfile in self.Map.ovlist: if os.path.isfile(ovlfile) and os.path.getsize(ovlfile): img = wx.Image(ovlfile, wx.BITMAP_TYPE_ANY) -# img.ConvertAlphaToMask() - ovlist.append(img) - self.imagedict[img] = ovlist.index(img) # set image PeudoDC ID - return ovlist + left = right = top = bottom = 0 + breakout = False +# # auto-crop scales and legends +# for w in range(img.GetWidth()): # set left edge +# for h in range(img.GetHeight()-1): +# if img.IsTransparent(w,h) == False: +# left = w +# breakout = True +# break +# if breakout: +# breakout = False +# break +# for w in range(img.GetWidth()-1, 0, -1): # set right edge +# for h in range(img.GetHeight()-1): +# if img.IsTransparent(w,h) == False: +# right = w +# breakout = True +# break +# if breakout: +# breakout = False +# break +# for h in range(img.GetHeight()): # set top edge +# for w in range(left,right): +# if img.IsTransparent(w,h) == False: +# top = h +# breakout = True +# break +# if breakout: +# breakout = False +# break +# for h in range(img.GetHeight()-1, 0, - 1): # set top edge +# for w in range(left,right): +# if img.IsTransparent(w,h) == False: +# bottom = h +# breakout = True +# break +# if breakout: +# breakout = False +# break +# cropwidth = right - left +# cropheight = bottom - top + pdc_id = self.Map.ovlist.index(ovlfile) +# self.ovldict[pdc_id] = img,(cropwidth,cropheight) # image and cropping information for each overlay image + self.ovldict[pdc_id] = img # image information for each overlay image + self.imagedict[img] = pdc_id # set image PeudoDC ID + return self.ovldict + + def GetImage(self): """ Converts files to wx.Image @@ -426,12 +474,11 @@ self.pdc.Clear() self.pdc.RemoveAll() self.Draw(self.pdc, self.img, drawid=id) # draw map image background - self.ovlist = self.GetOverlay() # list of decoration overlay images - if self.ovlist != []: # draw scale and legend overlays - for img in self.ovlist: - id = self.imagedict[img] + self.ovldict = self.GetOverlay() # list of decoration overlay images + if self.ovldict != {}: # draw scale and legend overlays + for id in self.ovldict: + img = self.ovldict[id] if id not in self.ovlcoords: self.ovlcoords[id] = wx.Rect(0,0,0,0) - if id == None: return # ID has not yet been assigned (image not painted) if id not in self.ovlchk: self.ovlchk[id] = False if self.ovlchk[id] == True: # draw any active and defined overlays self.Draw(self.pdc, img=img, drawid=id, @@ -480,10 +527,9 @@ self.pdc.SetBackground(wx.Brush(self.GetBackgroundColour())) r = self.pdc.GetIdBounds(id) self.pdc.TranslateId(id, dx, dy) - if id != 99: - r2 = self.pdc.GetIdBounds(id) - r = r.Union(r2) - r.Inflate(4,4) + r2 = self.pdc.GetIdBounds(id) + r = r.Union(r2) + r.Inflate(4,4) self.RefreshRect(r, False) self.lastpos = (event.GetX(),event.GetY()) @@ -520,31 +566,33 @@ wheel = event.GetWheelRotation() # +- int hitradius = 5 # distance for selecting map decorations - # left mouse button pressed + # left mouse button pressed; get decoration ID if event.LeftDown(): - self.mouse['begin'] = event.GetPositionTuple()[:] + self.lastpos = self.mouse['begin'] = event.GetPositionTuple()[:] + idlist = self.pdc.FindObjects(self.mouse['begin'][0], self.mouse['begin'][1], hitradius) + if idlist != []: + self.dragid = idlist[0] - # double click to select decoration for dragging + # double click to select overlay decoration options dialog elif event.ButtonDClick(): # start point of drag - self.lastpos = event.GetPositionTuple()[:] + clickposition = event.GetPositionTuple()[:] - # select decoration and get its ID + # get decoration ID # l = self.pdc.FindObjectsByBBox(self.mouse['begin'][0], self.mouse['begin'][1]) - idlist = self.pdc.FindObjects(self.lastpos[0], self.lastpos[1], hitradius) + idlist = self.pdc.FindObjects(clickposition[0], clickposition[1], hitradius) if idlist == []: return - id = idlist[0] - self.select[id] = not self.select[id] - if self.select[id] == True: - self.dragid = id - if id > 100: self.currtxtid = id - else: - self.ovlcoords[self.dragid] = self.pdc.GetIdBounds(self.dragid) - self.dragid = None - self.currtxtid = None - self.UpdateMap() - id = None + self.dragid = idlist[0] + self.ovlcoords[self.dragid] = self.pdc.GetIdBounds(self.dragid) + if self.dragid > 100: + self.currtxtid = self.dragid + self.Parent.addText(None) + elif self.dragid == 0: + self.Parent.addBarscale(None) + elif self.dragid == 1: + self.Parent.addLegend(None) + # left mouse button released and not just a pointer elif event.LeftUp(): @@ -558,7 +606,7 @@ self.render=True self.UpdateMap() - # digitizing + # digitizing elif self.parent.digittoolbar: if self.parent.digittoolbar.digitize == "point": east,north= self.Pixel2Cell(self.mouse['begin'][0],self.mouse['begin'][1]) @@ -567,7 +615,7 @@ self.render=True self.UpdateMap() - # quering + # querying elif self.mouse["box"] == "query": east,north = self.Pixel2Cell(self.mouse['begin'][0],self.mouse['begin'][1]) if self.parent.gismanager: @@ -583,15 +631,19 @@ else: print "Quering without gis manager not implemented yet" - + # end drag of overlay decoration elif self.dragid: - self.Refresh() + self.ovlcoords[self.dragid] = self.pdc.GetIdBounds(self.dragid) + self.dragid = None + self.currtxtid = None + id = None self.Update() elif event.Dragging(): currpos = event.GetPositionTuple()[:] end = (currpos[0]-self.mouse['begin'][0], \ currpos[1]-self.mouse['begin'][1]) + # dragging or drawing box with left button if self.mouse['box'] == 'pan': self.DragMap(end) @@ -618,16 +670,15 @@ x,y = event.GetPositionTuple()[:] #l = self.pdc.FindObjectsByBBox(x, y) l = self.pdc.FindObjects(x, y, hitradius) - if l: - id = l[0] - self.pdc.SetId(id) - if self.pdc.GetIdGreyedOut(id) == True: - self.pdc.SetIdGreyedOut(id, False) - else: - self.pdc.SetIdGreyedOut(id, True) - r = self.pdc.GetIdBounds(id) - r.Inflate(4,4) - self.RefreshRect(r, False) + if not l: return + id = l[0] + if self.pdc.GetIdGreyedOut(id) == True: + self.pdc.SetIdGreyedOut(id, False) + else: + self.pdc.SetIdGreyedOut(id, True) + r = self.pdc.GetIdBounds(id) + r.Inflate(4,4) + self.RefreshRect(r, False) # store current mouse position self.mouse['pos'] = event.GetPositionTuple()[:] @@ -858,11 +909,6 @@ self.ctrlbk.SetSelection(pgnum) event.Skip() -# def SetDcommandList(self, clst): -# self.MapWindow.dcmd_list = clst -# self.MapWindow.ProcessDcommand() -# self.MapWindow.UpdateMap() - def OnMotion(self, event): """ Mouse moved @@ -884,12 +930,6 @@ """ self.MapWindow.UpdateMap() -# def ReDrawCommand(self): -# """ -# d.* command on command line and enter pressed. -# """ -# self.MapWindow.UpdateMap() - def Pointer(self, event): """Pointer button clicled""" self.MapWindow.mouse['box'] = "point" @@ -952,7 +992,6 @@ self.Map.getRegion() self.Map.getResolution() self.UpdateMap() -# self.draw(dc) event.Skip() def OnAlignRegion(self, event): @@ -1079,17 +1118,16 @@ """ Handler for scale/arrow map decoration menu selection. """ - ovltype = 0 # index for overlay layer in render + ovltype = id = 0 # index for overlay layer in render if ovltype in self.params: params = self.params[ovltype] else: params = '' - ovlist = self.MapWindow.GetOverlay() - if ovlist == []: return - img = ovlist[0] - id = self.MapWindow.imagedict[img] - if id == None: return + ovldict = self.MapWindow.GetOverlay() + if id not in ovldict: return + img = ovldict[id] + if id not in self.ovlcoords: self.ovlcoords[id] = wx.Rect(0,0,0,0) # Decoration overlay control dialog @@ -1119,18 +1157,15 @@ """ Handler for legend map decoration menu selection. """ - ovltype = 1 # index for overlay layer in render + ovltype = id = 1 # index for overlay layer in render if ovltype in self.params: params = self.params[ovltype] else: params = '' - ovlist = self.MapWindow.GetOverlay() - if ovlist == []: return - img = ovlist[1] - id = self.MapWindow.imagedict[img] - if id == None: return - if id not in self.ovlcoords: self.ovlcoords[id] = wx.Rect(0,0,0,0) + ovldict = self.MapWindow.GetOverlay() + if id not in ovldict: return + img = ovldict[id] # Decoration overlay control dialog dlg = DecDialog(self, wx.ID_ANY, 'Legend', size=(350, 200), @@ -1185,6 +1220,8 @@ textfont = dlg.currFont textcolor = dlg.currClr + self.MapWindow.pdc.ClearId(id) + self.MapWindow.pdc.SetId(id) self.MapWindow.textdict[id] = (maptext,textfont,textcolor) self.MapWindow.Draw(self.MapWindow.pdc, img=self.MapWindow.textdict[id], drawid=id, pdctype='text', coords=textcoords) @@ -1233,7 +1270,7 @@ sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) box = wx.BoxSizer(wx.HORIZONTAL) - label = wx.StaticText(self, -1, ("Double-click %s with mouse in\npointer mode and drag to position.\nDouble-click again to set" % ctrltxt)) + label = wx.StaticText(self, -1, ("Drag %s with mouse in pointer mode\nto position. Double-click to change options" % ctrltxt)) box.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5) sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) @@ -1313,10 +1350,7 @@ sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) box = wx.BoxSizer(wx.HORIZONTAL) - label = wx.StaticText(self, -1, ("Double-click text with mouse in\ - \npointer mode and drag to position.\ - \nEdit by selecting text from overlay menu.\ - \nDouble-click again to set")) + label = wx.StaticText(self, -1, ("Drag text with mouse in pointer mode\nto position. Double-click to change options")) box.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5) sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) @@ -1338,18 +1372,9 @@ self.SetSizer(sizer) sizer.Fit(self) -# self.Bind(wx.EVT_CHECKBOX, self.onCheck, self.chkbox) self.Bind(wx.EVT_BUTTON, self.onSelectFont, fontbtn) self.textentry.Bind(wx.EVT_TEXT, self.onText) - -# def onCheck(self, event): -# """ -# Handler for checkbox for displaying/hiding decoration -# """ -# check = event.IsChecked() -# self.ovlchk[self.drawid] = check - def onText(self, event): self.currText = event.GetString() From barton at grass.itc.it Mon Apr 9 15:58:43 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Mon Apr 9 15:58:44 2007 Subject: [grass-addons] r460 - trunk/grassaddons/gui/gui_modules Message-ID: <200704091358.l39Dwh0W002257@grass.itc.it> Author: barton Date: 2007-04-09 15:58:34 +0200 (Mon, 09 Apr 2007) New Revision: 460 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py Log: Fix to keep overlay decoration in place with a map update. Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-09 06:24:25 UTC (rev 459) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-09 13:58:34 UTC (rev 460) @@ -632,7 +632,7 @@ print "Quering without gis manager not implemented yet" # end drag of overlay decoration - elif self.dragid: + elif self.dragid != None: self.ovlcoords[self.dragid] = self.pdc.GetIdBounds(self.dragid) self.dragid = None self.currtxtid = None From barton at grass.itc.it Mon Apr 9 21:16:22 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Mon Apr 9 21:16:24 2007 Subject: [grass-addons] r461 - trunk/grassaddons/gui/gui_modules Message-ID: <200704091916.l39JGMnW006136@grass.itc.it> Author: barton Date: 2007-04-09 21:16:16 +0200 (Mon, 09 Apr 2007) New Revision: 461 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py Log: Text rotation added for overlay decorations. Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-09 13:58:34 UTC (rev 460) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-09 19:16:16 UTC (rev 461) @@ -22,7 +22,7 @@ import wx import wx.aui -import os, sys, time, glob +import os, sys, time, glob, math import render import toolbars @@ -292,19 +292,38 @@ self.ovlcoords[drawid] = coords elif pdctype == 'text': # draw text on top of map - text = img[0] + rotation = float(img[3]) w,h = self.GetFullTextExtent(img[0])[0:2] - coords[2], coords[3] = coords[0] + w, coords[1] + h pdc.SetFont(img[1]) pdc.SetTextForeground(img[2]) - pdc.DrawText(img[0], coords[0], coords[1]) - pdc.SetIdBounds(drawid, (coords[0], coords[1], coords[2], coords[3])) + coords,w,h = self.textBounds(img,coords) + if rotation == 0: + pdc.DrawText(img[0], coords[0], coords[1]) + else: + pdc.DrawRotatedText(img[0], coords[0], coords[1], rotation) + pdc.SetIdBounds(drawid, (coords[0], coords[1], w, h)) self.ovlcoords[drawid] = coords pdc.EndDrawing() self.Refresh() + def textBounds(self, textinfo, coords): + rotation = float(textinfo[3]) + self.Update() + self.Refresh() + self.SetFont(textinfo[1]) + w,h = self.GetTextExtent(textinfo[0]) + if rotation == 0: + coords[2], coords[3] = coords[0] + w, coords[1] + h + return coords,w,h + else: + boxh = math.fabs(math.sin(math.radians(rotation)) * w) + h + boxw = math.fabs(math.cos(math.radians(rotation)) * w) + h + coords[2] = coords[0] + boxw + coords[3] = coords[1] + boxh + return coords,boxw,boxh + def OnPaint(self, event): """ All that is needed here is to draw the buffer to screen @@ -526,10 +545,16 @@ dy = event.GetY() - y self.pdc.SetBackground(wx.Brush(self.GetBackgroundColour())) r = self.pdc.GetIdBounds(id) + if self.dragid > 100: # text dragging + rtop = (r[0],r[1]-r[3],r[2],r[3]) + r = r.Union(rtop) + rleft = (r[0]-r[2],r[1],r[2],r[3]) + r = r.Union(rleft) self.pdc.TranslateId(id, dx, dy) r2 = self.pdc.GetIdBounds(id) r = r.Union(r2) r.Inflate(4,4) + self.Update() self.RefreshRect(r, False) self.lastpos = (event.GetX(),event.GetY()) @@ -564,12 +589,13 @@ Mouse motion and button click notifier """ wheel = event.GetWheelRotation() # +- int - hitradius = 5 # distance for selecting map decorations + hitradius = 10 # distance for selecting map decorations # left mouse button pressed; get decoration ID if event.LeftDown(): self.lastpos = self.mouse['begin'] = event.GetPositionTuple()[:] - idlist = self.pdc.FindObjects(self.mouse['begin'][0], self.mouse['begin'][1], hitradius) +# idlist = self.pdc.FindObjectsByBBox(self.lastpos[0],self.lastpos[1]) + idlist = self.pdc.FindObjects(self.lastpos[0],self.lastpos[1], hitradius) if idlist != []: self.dragid = idlist[0] @@ -579,7 +605,7 @@ clickposition = event.GetPositionTuple()[:] # get decoration ID -# l = self.pdc.FindObjectsByBBox(self.mouse['begin'][0], self.mouse['begin'][1]) +# idlist = self.pdc.FindObjectsByBBox(clickposition[0], clickposition[1]) idlist = self.pdc.FindObjects(clickposition[0], clickposition[1], hitradius) if idlist == []: return self.dragid = idlist[0] @@ -1128,7 +1154,7 @@ if id not in ovldict: return img = ovldict[id] - if id not in self.ovlcoords: self.ovlcoords[id] = wx.Rect(0,0,0,0) + if id not in self.ovlcoords: self.ovlcoords[id] = [10,10] # Decoration overlay control dialog dlg = DecDialog(self, wx.ID_ANY, 'Scale and North arrow', size=(350, 200), @@ -1167,6 +1193,8 @@ if id not in ovldict: return img = ovldict[id] + if id not in self.ovlcoords: self.ovlcoords[id] = [10,10] + # Decoration overlay control dialog dlg = DecDialog(self, wx.ID_ANY, 'Legend', size=(350, 200), style=wx.DEFAULT_DIALOG_STYLE, @@ -1198,7 +1226,8 @@ maptext = '' textfont = self.GetFont() textcolor = wx.BLACK - textcoords = [0,0,0,0] + textcoords = [10,10,10,10] + rotation = 0 if self.MapWindow.currtxtid == None: # text doesn't already exist id = wx.NewId()+100 @@ -1219,10 +1248,20 @@ maptext = dlg.currText textfont = dlg.currFont textcolor = dlg.currClr + rotation = dlg.currRot + coords,w,h = self.MapWindow.textBounds((maptext,textfont,textcolor,rotation),textcoords) + # delete object if if it has no text + if maptext == '': + self.MapWindow.pdc.ClearId(id) + self.MapWindow.pdc.RemoveId(id) + del self.MapWindow.textdict[id] + del self.ovlcoords[id] + return + self.MapWindow.pdc.ClearId(id) self.MapWindow.pdc.SetId(id) - self.MapWindow.textdict[id] = (maptext,textfont,textcolor) + self.MapWindow.textdict[id] = (maptext,textfont,textcolor,rotation) self.MapWindow.Draw(self.MapWindow.pdc, img=self.MapWindow.textdict[id], drawid=id, pdctype='text', coords=textcoords) self.MapWindow.Update() @@ -1324,19 +1363,18 @@ self.drawid = drawid if drawid in self.Parent.MapWindow.textdict: - self.currText,self.currFont,self.currClr = self.Parent.MapWindow.textdict[drawid] + self.currText,self.currFont,self.currClr,self.currRot = self.Parent.MapWindow.textdict[drawid] else: self.currClr = wx.BLACK self.currText = '' self.currFont = self.GetFont() + self.currRot = 0 sizer = wx.BoxSizer(wx.VERTICAL) box = wx.BoxSizer(wx.HORIZONTAL) - label = wx.StaticText(self, -1, "Enter text:") box.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5) - self.textentry = wx.TextCtrl(self, -1, "", size=(200,-1)) self.textentry.SetFont(self.currFont) self.textentry.SetForegroundColour(self.currClr) @@ -1345,6 +1383,16 @@ sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) box = wx.BoxSizer(wx.HORIZONTAL) + label = wx.StaticText(self, -1, "Rotation:") + box.Add(label, 0, wx.ALIGN_CENTRE|wx.ALL, 5) + self.rotation = wx.SpinCtrl(self, id=wx.ID_ANY, value="", pos=(30, 50), + size=(75,-1), style=wx.SP_ARROW_KEYS) + self.rotation.SetRange(-360,360) + self.rotation.SetValue(int(self.currRot)) + box.Add(self.rotation, 0, wx.ALIGN_CENTRE|wx.ALL, 5) + sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) + + box = wx.BoxSizer(wx.HORIZONTAL) fontbtn = wx.Button(self, wx.ID_ANY, "Set font") box.Add(fontbtn, 0, wx.ALIGN_CENTRE|wx.ALL, 5) sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) @@ -1374,10 +1422,14 @@ self.Bind(wx.EVT_BUTTON, self.onSelectFont, fontbtn) self.textentry.Bind(wx.EVT_TEXT, self.onText) + self.rotation.Bind(wx.EVT_TEXT, self.onRotation) def onText(self, event): self.currText = event.GetString() + def onRotation(self, event): + self.currRot = event.GetString() + def onSelectFont(self, event): data = wx.FontData() data.EnableEffects(True) From barton at grass.itc.it Tue Apr 10 05:18:51 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Tue Apr 10 05:18:52 2007 Subject: [grass-addons] r462 - trunk/grassaddons/gui/gui_modules Message-ID: <200704100318.l3A3IpTO012338@grass.itc.it> Author: barton Date: 2007-04-10 05:18:42 +0200 (Tue, 10 Apr 2007) New Revision: 462 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py Log: Clicking query button in map display tool bar will switch GIS Manager display to output console to display query results. Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-09 19:16:16 UTC (rev 461) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-10 03:18:42 UTC (rev 462) @@ -1076,6 +1076,10 @@ """ Query currrent or last map """ + # switch GIS Manager to output console to show query results + + self.Parent.notebook.SetSelection(1) + self.MapWindow.mouse['box'] = "query" self.MapWindow.zoomtype = 0 #event.Skip() From barton at grass.itc.it Tue Apr 10 06:44:46 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Tue Apr 10 06:44:47 2007 Subject: [grass-addons] r463 - trunk/grassaddons/gui/gui_modules Message-ID: <200704100444.l3A4ikNG013463@grass.itc.it> Author: barton Date: 2007-04-10 06:44:37 +0200 (Tue, 10 Apr 2007) New Revision: 463 Modified: trunk/grassaddons/gui/gui_modules/select.py Log: Attempt to fix bug where expanding selection tree in select control causes expand event to propagate to GIS Manager layer tree. Try this with line 131 active again and see what happens. Modified: trunk/grassaddons/gui/gui_modules/select.py =================================================================== --- trunk/grassaddons/gui/gui_modules/select.py 2007-04-10 03:18:42 UTC (rev 462) +++ trunk/grassaddons/gui/gui_modules/select.py 2007-04-10 04:44:37 UTC (rev 463) @@ -28,40 +28,47 @@ def Create(self, parent): - self.tree = wx.TreeCtrl(parent, style=wx.TR_HIDE_ROOT + self.seltree = wx.TreeCtrl(parent, style=wx.TR_HIDE_ROOT |wx.TR_HAS_BUTTONS |wx.TR_SINGLE |wx.TR_LINES_AT_ROOT |wx.SIMPLE_BORDER |wx.TR_FULL_ROW_HIGHLIGHT) - self.tree.Bind(wx.EVT_MOTION, self.OnMotion) - self.tree.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) + self.seltree.Bind(wx.EVT_MOTION, self.OnMotion) + self.seltree.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) + self.seltree.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.mapsetExpanded) + self.seltree.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.mapsetCollapsed) + def mapsetExpanded(self, event): + pass + def mapsetCollapsed(self, event): + pass + def GetControl(self): - return self.tree + return self.seltree def GetStringValue(self): if self.value: - return self.tree.GetItemText(self.value) + return self.seltree.GetItemText(self.value) return "" def OnPopup(self): if self.value: - self.tree.EnsureVisible(self.value) - self.tree.SelectItem(self.value) + self.seltree.EnsureVisible(self.value) + self.seltree.SelectItem(self.value) def SetStringValue(self, value): # this assumes that item strings are unique... - root = self.tree.GetRootItem() + root = self.seltree.GetRootItem() if not root: return found = self.FindItem(root, value) if found: self.value = found - self.tree.SelectItem(found) + self.seltree.SelectItem(found) def GetAdjustedSize(self, minWidth, prefHeight, maxHeight): return wx.Size(minWidth, min(200, maxHeight)) @@ -107,7 +114,7 @@ for dir in mapsets: if dir == curr_mapset: dir_node = self.AddItem('Mapset: '+dir) - self.tree.SetItemTextColour(dir_node,wx.Colour(50,50,200)) + self.seltree.SetItemTextColour(dir_node,wx.Colour(50,50,200)) try: elem_list = os.listdir(os.path.join (location_path, dir, element)) for elem in elem_list: @@ -121,11 +128,11 @@ # if self.layertype[self.layer_selected] == 'group': # KeyError: > # -------- ERROR END -------------- - #self.tree.Expand(dir_node) - + #self.seltree.Expand(dir_node) + else: dir_node = self.AddItem('Mapset: '+dir) - self.tree.SetItemTextColour(dir_node,wx.Colour(50,50,200)) + self.seltree.SetItemTextColour(dir_node,wx.Colour(50,50,200)) try: elem_list = os.listdir(os.path.join (location_path, dir, element)) for elem in elem_list: @@ -135,37 +142,37 @@ # helpers def FindItem(self, parentItem, text): - item, cookie = self.tree.GetFirstChild(parentItem) + item, cookie = self.seltree.GetFirstChild(parentItem) while item: - if self.tree.GetItemText(item) == text: + if self.seltree.GetItemText(item) == text: return item - if self.tree.ItemHasChildren(item): + if self.seltree.ItemHasChildren(item): item = self.FindItem(item, text) - item, cookie = self.tree.GetNextChild(parentItem, cookie) + item, cookie = self.seltree.GetNextChild(parentItem, cookie) return wx.TreeItemId(); def AddItem(self, value, parent=None): if not parent: - root = self.tree.GetRootItem() + root = self.seltree.GetRootItem() if not root: - root = self.tree.AddRoot("") + root = self.seltree.AddRoot("") parent = root - item = self.tree.AppendItem(parent, value) + item = self.seltree.AppendItem(parent, value) return item def OnMotion(self, evt): # have the selection follow the mouse, like in a real combobox - item, flags = self.tree.HitTest(evt.GetPosition()) + item, flags = self.seltree.HitTest(evt.GetPosition()) if item and flags & wx.TREE_HITTEST_ONITEMLABEL: - self.tree.SelectItem(item) + self.seltree.SelectItem(item) self.curitem = item evt.Skip() def OnLeftDown(self, evt): # do the combobox selection - item, flags = self.tree.HitTest(evt.GetPosition()) + item, flags = self.seltree.HitTest(evt.GetPosition()) if item and flags & wx.TREE_HITTEST_ONITEMLABEL: self.curitem = item self.value = item From barton at grass.itc.it Tue Apr 10 07:33:55 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Tue Apr 10 07:33:57 2007 Subject: [grass-addons] r464 - trunk/grassaddons/gui/gui_modules Message-ID: <200704100533.l3A5XtLv013560@grass.itc.it> Author: barton Date: 2007-04-10 07:33:47 +0200 (Tue, 10 Apr 2007) New Revision: 464 Modified: trunk/grassaddons/gui/gui_modules/select.py Log: Improve appearance select control. Modified: trunk/grassaddons/gui/gui_modules/select.py =================================================================== --- trunk/grassaddons/gui/gui_modules/select.py 2007-04-10 04:44:37 UTC (rev 463) +++ trunk/grassaddons/gui/gui_modules/select.py 2007-04-10 05:33:47 UTC (rev 464) @@ -12,6 +12,7 @@ wx.combo.ComboCtrl.__init__(self, parent=parent, id=id, size=size) tcp = TreeCtrlComboPopup() self.SetPopupControl(tcp) + self.SetPopupExtents(0,100) tcp.getElementList(type) class TreeCtrlComboPopup(wx.combo.ComboPopup): @@ -128,7 +129,7 @@ # if self.layertype[self.layer_selected] == 'group': # KeyError: > # -------- ERROR END -------------- - #self.seltree.Expand(dir_node) +# self.seltree.Expand(dir_node) else: dir_node = self.AddItem('Mapset: '+dir) From barton at grass.itc.it Tue Apr 10 16:42:22 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Tue Apr 10 16:42:24 2007 Subject: [grass-addons] r465 - trunk/grassaddons/gui/gui_modules Message-ID: <200704101442.l3AEgMCY019851@grass.itc.it> Author: barton Date: 2007-04-10 16:42:14 +0200 (Tue, 10 Apr 2007) New Revision: 465 Modified: trunk/grassaddons/gui/gui_modules/select.py Log: Additional dummy handler added to selection tree to stop bogus event propagation up to GIS Manager layer tree. Modified: trunk/grassaddons/gui/gui_modules/select.py =================================================================== --- trunk/grassaddons/gui/gui_modules/select.py 2007-04-10 05:33:47 UTC (rev 464) +++ trunk/grassaddons/gui/gui_modules/select.py 2007-04-10 14:42:14 UTC (rev 465) @@ -39,13 +39,24 @@ self.seltree.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) self.seltree.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.mapsetExpanded) self.seltree.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.mapsetCollapsed) + self.seltree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.mapsetActivated) + self.seltree.Bind(wx.EVT_TREE_SEL_CHANGED, self.mapsetSelected) + # the following dummy handler are needed to keep tree events from propagating up to + # the parent GIS Manager layer tree def mapsetExpanded(self, event): pass def mapsetCollapsed(self, event): pass + def mapsetActivated(self, event): + pass + + def mapsetSelected(self, event): + pass + # end of dummy events + def GetControl(self): return self.seltree From barton at grass.itc.it Tue Apr 10 16:54:58 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Tue Apr 10 16:55:00 2007 Subject: [grass-addons] r466 - trunk/grassaddons/gui/gui_modules Message-ID: <200704101454.l3AEswXR020750@grass.itc.it> Author: barton Date: 2007-04-10 16:54:47 +0200 (Tue, 10 Apr 2007) New Revision: 466 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py trunk/grassaddons/gui/gui_modules/toolbars.py Log: Button and dialog added for printing. Printing not enabled yet. Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-10 14:42:14 UTC (rev 465) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-10 14:54:47 UTC (rev 466) @@ -1043,6 +1043,29 @@ self.MapWindow.SaveToFile(dlg.GetPath(), wx.BITMAP_TYPE_PNG) dlg.Destroy() + def PrintMap(self, event): + """ + Print map display + """ + + pdata = wx.PrintDialogData() + + pdata.EnableSelection(True) + pdata.EnablePrintToFile(True) + pdata.EnablePageNumbers(True) + pdata.SetMinPage(1) + pdata.SetMaxPage(5) + pdata.SetAllPages(True) + + dlg = wx.PrintDialog(self, pdata) + + if dlg.ShowModal() == wx.ID_OK: +# data = dlg.GetPrintDialogData() + print 'printing not yet enabled' +# self.log.WriteText('GetAllPages: %d\n' % data.GetAllPages()) + + dlg.Destroy() + def OnCloseWindow(self, event): """ Window closed Modified: trunk/grassaddons/gui/gui_modules/toolbars.py =================================================================== --- trunk/grassaddons/gui/gui_modules/toolbars.py 2007-04-10 14:42:14 UTC (rev 465) +++ trunk/grassaddons/gui/gui_modules/toolbars.py 2007-04-10 14:54:47 UTC (rev 466) @@ -91,6 +91,14 @@ bmpDisabled=wx.NullBitmap, kind=wx.ITEM_NORMAL, shortHelp="Save display to PNG file", longHelp="") + self.printmap = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="printmap", + #bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"file-save.gif"), + #wx.BITMAP_TYPE_ANY), + # just testing wx.ArtProvider + bitmap=wx.ArtProvider.GetBitmap(id=wx.ART_PRINT, client=wx.ART_BUTTON), + bmpDisabled=wx.NullBitmap, kind=wx.ITEM_NORMAL, + shortHelp="Print display", longHelp="") + self.toolbar.AddSeparator() # @@ -111,6 +119,7 @@ self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnQuery, self.query) self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnErase, self.erase) self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.SaveToFile, self.savefile) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.PrintMap, self.printmap) self.mapdisplay.Bind(wx.EVT_COMBOBOX, self.OnSelect, self.combo) def OnSelect(self,event): From barton at grass.itc.it Wed Apr 11 08:29:51 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Wed Apr 11 08:29:56 2007 Subject: [grass-addons] r467 - trunk/grassaddons/gui/gui_modules Message-ID: <200704110629.l3B6TpAU031661@grass.itc.it> Author: barton Date: 2007-04-11 08:29:37 +0200 (Wed, 11 Apr 2007) New Revision: 467 Modified: trunk/grassaddons/gui/gui_modules/menuform.py Log: Added checkbox for potentially transparent colors, indicated by keyword "none". But am not sure how to parse it in the color section. Commented out at line 660. Modified: trunk/grassaddons/gui/gui_modules/menuform.py =================================================================== --- trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-10 14:54:47 UTC (rev 466) +++ trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-11 06:29:37 UTC (rev 467) @@ -618,7 +618,7 @@ which_sizer.Add(txt, 0, wx.ADJUST_MINSIZE | wx.ALL, 5) # element selection tree combobox (maps, icons, regions, etc.) if p['prompt'] != 'color': - self.selection = select.Select(which_panel, id=wx.ID_ANY, size=(250,-1), + self.selection = select.Select(which_panel, id=wx.ID_ANY, size=(300,-1), type=p['element']) if p['value'] != '': self.selection.SetValue(p['value']) # parameter previously set which_sizer.Add(self.selection, 0, wx.ADJUST_MINSIZE| wx.ALL, 5) @@ -657,6 +657,16 @@ which_sizer.Add(btn_colour, 0, wx.ADJUST_MINSIZE| wx.ALL, 5) self.paramdict[btn_colour] = ID_PARAM_START + p_count self.Bind(csel.EVT_COLOURSELECT, self.OnColorButton, btn_colour) +# if "none" in title: +# none_check = wx.CheckBox(which_panel, wx.ID_ANY, "Transparent") +# if p['value'] != '' and p['value'][0] == "none": +# none_check.SetValue(True) +# else: +# none_check.SetValue(False) +# none_check.SetFont( wx.Font( 12, wx.FONTFAMILY_DEFAULT, wx.NORMAL, text_style, 0, '')) +# which_sizer.Add(none_check, 0, wx.ADJUST_MINSIZE| wx.ALL, 5) +# self.paramdict[none_check] = ID_PARAM_START + p_count +# self.Bind(wx.EVT_CHECKBOX, self.EvtCheckBox, none_check) if txt is not None: txt.SetFont( wx.Font( 12, wx.FONTFAMILY_DEFAULT, wx.NORMAL, text_style, 0, '')) From cepicky at grass.itc.it Wed Apr 11 12:05:40 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Wed Apr 11 12:05:47 2007 Subject: [grass-addons] r468 - trunk/grassaddons/gui/gui_modules Message-ID: <200704111005.l3BA5eHi002938@grass.itc.it> Author: cepicky Date: 2007-04-11 12:05:39 +0200 (Wed, 11 Apr 2007) New Revision: 468 Modified: trunk/grassaddons/gui/gui_modules/sqlbuilder.py Log: sql query working better Modified: trunk/grassaddons/gui/gui_modules/sqlbuilder.py =================================================================== --- trunk/grassaddons/gui/gui_modules/sqlbuilder.py 2007-04-11 06:29:37 UTC (rev 467) +++ trunk/grassaddons/gui/gui_modules/sqlbuilder.py 2007-04-11 10:05:39 UTC (rev 468) @@ -26,6 +26,7 @@ # variables # self.vectmap = vectmap + print self.vectmap if not "@" in self.vectmap: self.vectmap = self.vectmap+"@"+grassenv.env["MAPSET"] self.mapname, self.mapset = self.vectmap.split("@") From cepicky at grass.itc.it Wed Apr 11 18:07:56 2007 From: cepicky at grass.itc.it (cepicky@grass.itc.it) Date: Wed Apr 11 18:07:57 2007 Subject: [grass-addons] r469 - in trunk/grassaddons/gui: . images Message-ID: <200704111607.l3BG7uvh006810@grass.itc.it> Author: cepicky Date: 2007-04-11 18:07:55 +0200 (Wed, 11 Apr 2007) New Revision: 469 Added: trunk/grassaddons/gui/images/wizard.png Modified: trunk/grassaddons/gui/location_wizard.py trunk/grassaddons/gui/wxgui.py Log: reorderd wizard Added: trunk/grassaddons/gui/images/wizard.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/images/wizard.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Modified: trunk/grassaddons/gui/location_wizard.py =================================================================== --- trunk/grassaddons/gui/location_wizard.py 2007-04-11 10:05:39 UTC (rev 468) +++ trunk/grassaddons/gui/location_wizard.py 2007-04-11 16:07:55 UTC (rev 469) @@ -177,10 +177,11 @@ self.datumlist.SetColumnWidth(3, wx.LIST_AUTOSIZE) def onPageChange(self,event): - global datum - datum = self.tdatum.GetValue() - global transform - transform = self.ttrans.GetValue() + self.GetNext().SetPrev(self) + global datum + datum = self.tdatum.GetValue() + global transform + transform = self.ttrans.GetValue() def OnDoSearch(self,event): str = self.searchb.GetValue() @@ -343,20 +344,33 @@ global west global resolution + if not coordsys: + coordsys = 0 + if not north: + north = 0 + if not south: + south = 0 + if not east: + east = 0 + if not west: + west = 0 + if not resolution: + resolution = 1 + #if projection != "latlong": rows = int(round((float(north)-float(south))/float(resolution))) cols = int(round((float(east)-float(west))/float(resolution))) cells = int(rows*cols) - self.ldatabase.SetLabel(database) - self.llocation.SetLabel(location) - self.lprojection.SetLabel(coordsys) - self.lnorth.SetLabel(north) - self.lsouth.SetLabel(south) - self.least.SetLabel(east) - self.lwest.SetLabel(west) - self.lres.SetLabel(resolution) + self.ldatabase.SetLabel(str(database)) + self.llocation.SetLabel(str(location)) + self.lprojection.SetLabel(str(coordsys)) + self.lnorth.SetLabel(str(north)) + self.lsouth.SetLabel(str(south)) + self.least.SetLabel(str(east)) + self.lwest.SetLabel(str(west)) + self.lres.SetLabel(str(resolution)) self.lrows.SetLabel(str(rows)) self.lcols.SetLabel(str(cols)) self.lcells.SetLabel(str(cells)) @@ -684,6 +698,7 @@ def onPageChange(self, event): self.GetNext().FillVars() + self.GetNext().SetPrev(self) global north north = self.ttop.GetValue() @@ -888,7 +903,8 @@ class EPSGPage(TitledPage): def __init__(self, wizard, parent): TitledPage.__init__(self, wizard, "Choose EPSG Code") - wx.MessageBox("in epsgpage") + #wx.MessageBox("in epsgpage") + self.parent = parent # labels self.lfile= wx.StaticText(self, -1, "Path to the EPSG-codes file: ", @@ -963,8 +979,9 @@ def onPageChange(self, event): global epsgcode - epsgcode = self.tcode - wx.MessageBox("setting epsgcode to %" % (epsgcode)) + epsgcode = self.tcode.GetValue() + self.parent.datumpage.SetPrev(self) + #wx.MessageBox("setting epsgcode to %s" % (epsgcode)) def OnDoSearch(self,event): str = self.searchb.GetValue() @@ -1038,7 +1055,7 @@ dlg.Destroy() def OnChange(self,event): - self.item = event.GetItem() + self.item = event.GetItem() def OnDoubleClick(self, event): print self.epsgs.GetValue() @@ -1090,13 +1107,13 @@ self.parent.bboxpage.SetPrev(self.parent.datumpage) elif event.GetId() == self.radio3.GetId(): coordsys = "utm" - self.SetNext(self.parent.datumpage) - self.parent.datumpage.SetPrev(self.parent.csystemspage) - self.parent.bboxpage.SetPrev(self.parent.datumpage) + self.SetNext(self.parent.utmpage) + self.parent.datumpage.SetPrev(self.parent.utmpage) elif event.GetId() == self.radio4.GetId(): coordsys = "custom" self.SetNext(self.parent.projpage) - self.parent.bboxpage.SetPrev(self.parent.projpage) + self.parent.datumpage.SetPrev(self.parent.projpage) + self.parent.bboxpage.SetPrev(self.parent.datumpage) elif event.GetId() == self.radio5.GetId(): coordsys = "epsg" self.SetNext(self.parent.epsgpage) @@ -1112,7 +1129,24 @@ self.parent.bboxpage.cstate.Enable(False) else: self.parent.bboxpage.cstate.Enable(True) + +class UTMPage(TitledPage): + def __init__(self, wizard, parent): + TitledPage.__init__(self, wizard, "Choose zone for UTM coordinate system") + self.parent = parent + self.text_utm = self.MakeTextCtrl(size=(300,-1)) + self.label_utm= self.MakeLabel("Set your UTM zone: ") + + self.sizer.Add(self.label_utm, 0, wx.ALIGN_LEFT, 5, row=1,col=1) + self.sizer.Add(self.text_utm, 0, wx.ALIGN_LEFT, 5, row=1,col=2) + + + def GetUtm(self): + return int(self.text_utm.GetValue()) + + + class DatabasePage(TitledPage): def __init__(self, wizard, parent, grassdatabase): TitledPage.__init__(self, wizard, "Define GRASS database and new Location Name") @@ -1189,8 +1223,8 @@ class GWizard: def __init__(self, parent, grassdatabase): - wizbmp = wx.Image(os.path.join(os.getenv("GISBASE"),"etc","wx","images","grasslogo_small.gif"), wx.BITMAP_TYPE_GIF) - wizbmp.Rescale(50,60) + wizbmp = wx.Image(os.path.join(os.getenv("GISBASE"),"etc","wx","images","wizard.png"), wx.BITMAP_TYPE_PNG) + wizbmp.Rescale(250,600) wizbmp = wizbmp.ConvertToBitmap() wizard = wiz.Wizard(parent, -1, "Define new Location", bitmap=wizbmp) @@ -1202,15 +1236,17 @@ self.projpage = ProjectionsPage(wizard, self) self.sumpage = SummaryPage(wizard, self) self.datumpage = DatumPage(wizard, self) + self.utmpage = UTMPage(wizard,self) # Set the initial order of the pages + # it should follow the epsg line self.startpage.SetNext(self.csystemspage) self.csystemspage.SetPrev(self.startpage) self.csystemspage.SetNext(self.bboxpage) self.epsgpage.SetPrev(self.csystemspage) - self.epsgpage.SetNext(self.sumpage) + self.epsgpage.SetNext(self.datumpage) self.projpage.SetPrev(self.csystemspage) self.projpage.SetNext(self.datumpage) @@ -1225,6 +1261,9 @@ self.sumpage.SetPrev(self.bboxpage) + self.utmpage.SetPrev(self.csystemspage) + self.utmpage.SetNext(self.datumpage) + wizard.FitToPage(self.bboxpage) if wizard.RunWizard(self.startpage): self.onWizFinished() Modified: trunk/grassaddons/gui/wxgui.py =================================================================== --- trunk/grassaddons/gui/wxgui.py 2007-04-11 10:05:39 UTC (rev 468) +++ trunk/grassaddons/gui/wxgui.py 2007-04-11 16:07:55 UTC (rev 469) @@ -343,7 +343,7 @@ from gui_modules import dbm self.dbmanager = gui_modules.dbm.AttributeManager(self, - -1,"GRASS Attribute Table Manager: %s" % map, + -1,"GRASS Attribute Table Manager: %s" % mapname, size=wx.Size(500,300),vectmap=mapname, pointdata=pointdata) From barton at grass.itc.it Thu Apr 12 00:18:45 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Thu Apr 12 00:18:46 2007 Subject: [grass-addons] r470 - trunk/grassaddons/gui/gui_modules Message-ID: <200704112218.l3BMIj2c010415@grass.itc.it> Author: barton Date: 2007-04-12 00:18:34 +0200 (Thu, 12 Apr 2007) New Revision: 470 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py trunk/grassaddons/gui/gui_modules/toolbars.py Log: Added zoom back button and methods. Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-11 16:07:55 UTC (rev 469) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-11 22:18:34 UTC (rev 470) @@ -196,6 +196,13 @@ self.currtxtid = None # PseudoDC id for currently selected text # + # Zoom objects + # + self.zoomhistory = [] # list of past zoom extents + self.currzoom = 0 # current set of extents in zoom history being used + + + # # mouse attributes like currently pressed buttons, position on # the screen, begin and end of dragging, and type of drawing # @@ -253,11 +260,14 @@ self.Refresh() if pdctype == 'clear': # erase the display + bg = wx.Brush(self.GetBackgroundColour()) + pdc.SetBackground(bg) + pdc.Clear() + self.Refresh() pdc.EndDrawing() return if pdctype == 'image': - mask = None bitmap = wx.BitmapFromImage(img) # if drawid in self.ovldict: # w,h = self.ovldict[drawid][1] @@ -400,8 +410,8 @@ if os.path.isfile(ovlfile) and os.path.getsize(ovlfile): img = wx.Image(ovlfile, wx.BITMAP_TYPE_ANY) - left = right = top = bottom = 0 - breakout = False +# left = right = top = bottom = 0 +# breakout = False # # auto-crop scales and legends # for w in range(img.GetWidth()): # set left edge # for h in range(img.GetHeight()-1): @@ -511,9 +521,9 @@ self.resize = False # update statusbar - self.parent.statusbar.SetStatusText("Extent: %d,%d : %d,%d" % - (self.Map.region["w"], self.Map.region["e"], - self.Map.region["n"], self.Map.region["s"]), 0) + self.parent.statusbar.SetStatusText("Extents: %d(W)-%d(E), %d(N)-%d(S)" % + (self.Map.region["w"], self.Map.region["e"], + self.Map.region["n"], self.Map.region["s"]), 0) def EraseMap(self): """ @@ -764,6 +774,30 @@ self.Map.region['e'] = newreg['e'] self.Map.region['w'] = newreg['w'] + self.ZoomHistory(newreg['n'],newreg['s'],newreg['e'],newreg['w']) + + def ZoomBack(self): + + if len(self.zoomhistory) > 0: + self.zoomhistory.pop() + zoom = self.zoomhistory[len(self.zoomhistory)-1] + + if zoom: + self.Map.region['n'] = zoom[0] + self.Map.region['s'] = zoom[1] + self.Map.region['e'] = zoom[2] + self.Map.region['w'] = zoom[3] + self.render=True + self.UpdateMap() + + def ZoomHistory(self, n,s,e,w): + """ + Manages a list of last 10 zoom extents + """ + self.zoomhistory.append((n,s,e,w)) + if len(self.zoomhistory) > 10: + self.zoomhistory.pop(0) + class MapFrame(wx.Frame): """ Main frame for map display window. Drawing takes place in child double buffered @@ -826,7 +860,7 @@ # self.statusbar = self.CreateStatusBar(number=2, style=0) self.statusbar.SetStatusWidths([-2, -1]) - map_frame_statusbar_fields = ["Extent: %d,%d : %d,%d" % + map_frame_statusbar_fields = ["Extents: %d(W)-%d(E), %d(N)-%d(S)" % (self.Map.region["w"], self.Map.region["e"], self.Map.region["n"], self.Map.region["s"]), "%s,%s" %(None, None)] @@ -847,6 +881,11 @@ self.MapWindow = BufferedWindow(self, id = wx.ID_ANY) # initialize buffered DC self.MapWindow.Bind(wx.EVT_MOTION, self.OnMotion) + # + # Init zoomhistory + # + self.MapWindow.ZoomHistory(self.Map.region['n'],self.Map.region['s'],self.Map.region['e'],self.Map.region['w']) + # decoration overlays self.ovlchk = self.MapWindow.ovlchk self.ovlcoords = self.MapWindow.ovlcoords @@ -905,9 +944,6 @@ self.width, self.height = self.GetClientSize() self.Map.geom = self.width, self.height self.Map.GetRegion() - #FIXME - #This was Map.getResolution(). - #I'm guessing at the moment that this is replaced by Map.SetRegion() self.Map.SetRegion() def OnFocus(self, event): @@ -991,8 +1027,7 @@ """ Zoom last (previously stored position) """ - # FIXME - pass + self.MapWindow.ZoomBack() def OnPan(self, event): """ Modified: trunk/grassaddons/gui/gui_modules/toolbars.py =================================================================== --- trunk/grassaddons/gui/gui_modules/toolbars.py 2007-04-11 16:07:55 UTC (rev 469) +++ trunk/grassaddons/gui/gui_modules/toolbars.py 2007-04-11 22:18:34 UTC (rev 470) @@ -69,6 +69,11 @@ wx.BITMAP_TYPE_ANY), bmpDisabled=wx.NullBitmap, kind=wx.ITEM_RADIO, shortHelp="Query", longHelp="Query selected map") + self.zoomback = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="zoom_back", + bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"gui-zoom_back.gif"), + wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, kind=wx.ITEM_NORMAL, + shortHelp="Zoom back", longHelp="Zoom to previous display region") self.toolbar.AddSeparator() @@ -115,6 +120,7 @@ self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnZoomIn, self.zoomin) self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnZoomOut, self.zoomout) self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnPan, self.pan) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnZoomBack, self.zoomback) self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.onDecoration, self.dec) self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnQuery, self.query) self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnErase, self.erase) From barton at grass.itc.it Thu Apr 12 00:19:19 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Thu Apr 12 00:19:20 2007 Subject: [grass-addons] r471 - trunk/grassaddons/gui/gui_modules Message-ID: <200704112219.l3BMJJPw010435@grass.itc.it> Author: barton Date: 2007-04-12 00:19:08 +0200 (Thu, 12 Apr 2007) New Revision: 471 Modified: trunk/grassaddons/gui/gui_modules/render.py Log: very minor code cleanup Modified: trunk/grassaddons/gui/gui_modules/render.py =================================================================== --- trunk/grassaddons/gui/gui_modules/render.py 2007-04-11 22:18:34 UTC (rev 470) +++ trunk/grassaddons/gui/gui_modules/render.py 2007-04-11 22:19:08 UTC (rev 471) @@ -379,7 +379,7 @@ def __adjustRegion(self): """ Adjust region according to monitor size - """ + """ # adjusting region to monitor size if self.width > self.height and \ @@ -682,7 +682,7 @@ layer = MapLayer(type="raster", name=name, mapset=mapset, active=l_active, hidden=l_hidden, opacity=l_opacity, dispcmd=dispcmd) - + layer.id = len(self.layers)-1 # add maplayer to the list of layers From barton at grass.itc.it Thu Apr 12 00:21:33 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Thu Apr 12 00:21:34 2007 Subject: [grass-addons] r472 - trunk/grassaddons/gui/gui_modules Message-ID: <200704112221.l3BMLXNc010456@grass.itc.it> Author: barton Date: 2007-04-12 00:21:24 +0200 (Thu, 12 Apr 2007) New Revision: 472 Modified: trunk/grassaddons/gui/gui_modules/toolbars.py Log: moved zoomback button Modified: trunk/grassaddons/gui/gui_modules/toolbars.py =================================================================== --- trunk/grassaddons/gui/gui_modules/toolbars.py 2007-04-11 22:19:08 UTC (rev 471) +++ trunk/grassaddons/gui/gui_modules/toolbars.py 2007-04-11 22:21:24 UTC (rev 472) @@ -59,6 +59,11 @@ wx.BITMAP_TYPE_ANY), bmpDisabled=wx.NullBitmap, kind=wx.ITEM_RADIO, shortHelp="Zoom out", longHelp="Drag or click mouse to unzoom") + self.zoomback = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="zoom_back", + bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"gui-zoom_back.gif"), + wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, kind=wx.ITEM_NORMAL, + shortHelp="Zoom back", longHelp="Zoom to previous display region") self.pan = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="pan", bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"gui-pan.gif"), wx.BITMAP_TYPE_ANY), @@ -69,11 +74,6 @@ wx.BITMAP_TYPE_ANY), bmpDisabled=wx.NullBitmap, kind=wx.ITEM_RADIO, shortHelp="Query", longHelp="Query selected map") - self.zoomback = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="zoom_back", - bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"gui-zoom_back.gif"), - wx.BITMAP_TYPE_ANY), - bmpDisabled=wx.NullBitmap, kind=wx.ITEM_NORMAL, - shortHelp="Zoom back", longHelp="Zoom to previous display region") self.toolbar.AddSeparator() From calvelo at grass.itc.it Thu Apr 12 00:24:32 2007 From: calvelo at grass.itc.it (calvelo@grass.itc.it) Date: Thu Apr 12 00:24:33 2007 Subject: [grass-addons] r473 - trunk/grassaddons/gui/gui_modules Message-ID: <200704112224.l3BMOWQM010491@grass.itc.it> Author: calvelo Date: 2007-04-12 00:24:26 +0200 (Thu, 12 Apr 2007) New Revision: 473 Modified: trunk/grassaddons/gui/gui_modules/menuform.py Log: - transparent color now works - internal structures changed to manage the widget-parameter relation - simplified event callbacks: each special callback sets the 'value' field and calls updateStatus now - statusline report and copy button both work - commenting and cosmetics - some better error handling Modified: trunk/grassaddons/gui/gui_modules/menuform.py =================================================================== --- trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-11 22:21:24 UTC (rev 472) +++ trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-11 22:24:26 UTC (rev 473) @@ -51,6 +51,9 @@ import os from os import system +import gettext +#from gettext import _ +gettext.install("wxgrass") sys.path.append(os.path.join(os.getenv("GISBASE"),"etc","wx")) try: @@ -107,13 +110,27 @@ (128, 0,128) ) t_color = t_colors.split(',') -color_str2rgb = {} -color_rgb2str = {} +str2rgb = {} +rgb2str = {} for c in range(0,len(t_rgb)): - color_str2rgb[ t_color[c] ] = t_rgb[ c ] - color_rgb2str[ t_rgb[ c ] ] = t_color[ c ] + str2rgb[ t_color[c] ] = t_rgb[ c ] + rgb2str[ t_rgb[ c ] ] = t_color[ c ] +def color_resolve(color): + if color[0] in "0123456789": + rgb = tuple(map(int,color.split( ':' ))) + label = color + else: + # Convert color names to RGB + try: + rgb = str2rgb[ color ] + label = color + except KeyError: + rgb = (200,200,200) + label = 'Select Color' + return (rgb, label) + def normalize_whitespace(text): "Remove redundant whitespace from a string" return string.join( string.split(text), ' ') @@ -152,6 +169,10 @@ self.description = '' self.flags = [] + def buildCmd(self): # TODO: It should be this class' responsibility to build the command, not the gui's. + pass + + class processTask(HandlerBase): """A SAX handler for the --interface-description output, as defined in grass-interface.dtd. Extend or modify this and the @@ -308,8 +329,8 @@ self.SetPage( "".join( contents ) ) self.Ok = True except: - raise self.Ok = False + raise class mainFrame(wx.Frame): @@ -413,11 +434,11 @@ self.OnCancel(event) def OnApply(self, event): - cmd,params = self.createCmd() + cmd = self.createCmd() if cmd is not None and self.get_dcmd is not None: # return d.* command to layer tree for rendering - self.get_dcmd(cmd, self.layer,params) + self.get_dcmd(cmd, self.layer, {"params":self.task.params,"flags":self.task.flags} ) # echo d.* command to output console # self.parent.writeDCommand(cmd) return cmd @@ -492,11 +513,10 @@ self.task = task self.selection = '' #selection from GIS element selector - self.paramdict = {} # dictionary of controls and their parameter values + # Determine tab layout sections = ['Main'] is_section = {} - for task in self.task.params + self.task.flags: if not task.has_key('guisection') or task['guisection']=='': task['guisection'] = 'Options' @@ -513,20 +533,18 @@ sections = sections[1:] self.panelsizer = wx.BoxSizer(wx.VERTICAL) - + # Build notebook nbStyle=FN.FNB_NO_X_BUTTON|FN.FNB_NO_NAV_BUTTONS|FN.FNB_VC8|FN.FNB_BACKGROUND_GRADIENT self.notebook = FN.FlatNotebook( self, id=wx.ID_ANY, style=nbStyle) self.notebook.SetTabAreaColour(wx.Colour(125,200,175)) self.notebook.Bind( FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnPageChange ) self.tab = {} self.tabsizer = {} - is_first = True for section in sections: self.tab[section] = wx.ScrolledWindow(self.notebook, id = wx.ID_ANY ) self.tab[section].SetScrollRate(10,10) self.tabsizer[section] = wx.BoxSizer(wx.VERTICAL) - self.notebook.AddPage( self.tab[section], text = section, select = is_first ) - is_first = False + self.notebook.AddPage( self.tab[section], text = section ) # are we running from command line? if standalone: @@ -542,9 +560,8 @@ self.notebook.SetSelection(0) self.panelsizer.Add( self.notebook, 1, flag=wx.EXPAND ) - p_count = -1 + for p in self.task.params: - p_count += 1 # Needed for checkboxes hack which_sizer = self.tabsizer[ p['guisection'] ] which_panel = self.tab[ p['guisection'] ] title = text_beautify(p['description']) @@ -562,18 +579,18 @@ if p['multiple'] == 'yes': txt = wx.StaticBox(which_panel,0,title+":") hSizer=wx.StaticBoxSizer( txt, wx.VERTICAL ) - v_count = 0 isDefault = {} for defval in p['value'].split(','): isDefault[ defval ] = 'yes' + # for multi checkboxes, this is an array of all wx IDs + # for each individual checkbox + p[ 'wxId' ]=[] for val in valuelist: - # This is the checkboxes hack - idForWX = ID_MULTI_START + p_count*20 + v_count - chkbox = wx.CheckBox( which_panel, idForWX, text_beautify(val) ) + chkbox = wx.CheckBox( which_panel, label = text_beautify(val) ) + p[ 'wxId' ].append( chkbox.GetId() ) if isDefault.has_key(val): chkbox.SetValue( True ) hSizer.Add( chkbox,0,wx.ADJUST_MINSIZE,5 ) - self.Bind(wx.EVT_CHECKBOX, self.EvtCheckBoxMulti) - v_count += 1 + self.Bind(wx.EVT_CHECKBOX, self.OnCheckBoxMulti) which_sizer.Add( hSizer, 0, wx.ADJUST_MINSIZE, 5) elif len(valuelist) == 1: txt = wx.StaticText(which_panel, label = title + @@ -584,8 +601,8 @@ if p['value'] != '': self.txt2.SetValue(p['value']) # parameter previously set which_sizer.Add(self.txt2, 0, wx.ADJUST_MINSIZE, 5) - self.paramdict[self.txt2] = ID_PARAM_START + p_count - self.txt2.Bind(wx.EVT_TEXT, self.EvtText) + p['wxId'] = self.txt2.GetId() + self.txt2.Bind(wx.EVT_TEXT, self.OnSetValue) else: txt = wx.StaticText(which_panel, label = title + ':' ) which_sizer.Add(txt, 0, wx.ADJUST_MINSIZE | wx.ALL, 5) @@ -594,8 +611,8 @@ valuelist, wx.CB_DROPDOWN) if p['value'] != '': self.cb.SetValue(p['value']) # parameter previously set which_sizer.Add(self.cb, 0, wx.ADJUST_MINSIZE, 5) - self.paramdict[self.cb] = ID_PARAM_START + p_count - self.cb.Bind( wx.EVT_COMBOBOX, self.EvtComboBox) + p['wxId'] = self.cb.GetId() + self.cb.Bind( wx.EVT_COMBOBOX, self.OnSetValue) # text entry if (p['type'] in ('string','integer','float') @@ -610,8 +627,8 @@ size = (STRING_ENTRY_WIDTH, ENTRY_HEIGHT)) if p['value'] != '': self.txt3.SetValue(p['value']) # parameter previously set which_sizer.Add(self.txt3, 0, wx.ADJUST_MINSIZE| wx.ALL, 5) - self.paramdict[self.txt3] = ID_PARAM_START + p_count - self.txt3.Bind(wx.EVT_TEXT, self.EvtText) + p['wxId'] = self.txt3.GetId() + self.txt3.Bind(wx.EVT_TEXT, self.OnSetValue) if p['type'] == 'string' and p['gisprompt'] == True: txt = wx.StaticText(which_panel, label = title + ':') @@ -622,57 +639,41 @@ type=p['element']) if p['value'] != '': self.selection.SetValue(p['value']) # parameter previously set which_sizer.Add(self.selection, 0, wx.ADJUST_MINSIZE| wx.ALL, 5) - self.paramdict[self.selection] = ID_PARAM_START + p_count - self.selection.Bind(wx.EVT_TEXT, self.EvtText) + p['wxId'] = self.selection.GetId() + self.selection.Bind(wx.EVT_TEXT, self.OnSetValue) # color entry elif p['prompt'] == 'color': if p['default'] != '': - if p['default'][0] in "0123456789": - default_color = tuple(map(int,p['default'].split( ':' ))) - label_color = p['default'] - else: - # Convert color names to RGB - try: - default_color = color_str2rgb[ p['default'] ] - label_color = p['default'] - except KeyError: - default_color = (200,200,200) - label_color = 'Select Color' - else: - default_color = (200,200,200) - label_color = 'Select Color' + default_color, label_color = color_resolve( p['default'] ) if p['value'] != '': # parameter previously set - if p['value'][0] in "0123456789": - default_color = tuple(map(int,p['value'].split( ':' ))) - label_color = p['value'] + default_color, label_color = color_resolve( p['value'] ) + if "none" in title: + this_sizer = wx.BoxSizer( wx.HORIZONTAL ) + else: + this_sizer = which_sizer + btn_colour = csel.ColourSelect(which_panel, wx.ID_ANY, label_color, default_color, wx.DefaultPosition, (150,-1) ) + this_sizer.Add(btn_colour, 0, wx.ADJUST_MINSIZE| wx.ALL, 5) + # For color selectors, this is a two-member array, holding the IDs of + # the selector proper and either a "transparent" button or None + p['wxId'] = [btn_colour.GetId(),] + btn_colour.Bind(csel.EVT_COLOURSELECT, self.OnColorChange ) + if "none" in title: + none_check = wx.CheckBox(which_panel, wx.ID_ANY, "Transparent") + if p['value'] != '' and p['value'][0] == "none": + none_check.SetValue(True) else: - # Convert color names to RGB - try: - default_color = color_str2rgb[ p['value'] ] - label_color = p['value'] - except KeyError: - default_color = (200,200,200) - label_color = 'Select Color' - btn_colour = csel.ColourSelect(which_panel, -1, label_color, default_color, wx.DefaultPosition, (150,-1) ) - which_sizer.Add(btn_colour, 0, wx.ADJUST_MINSIZE| wx.ALL, 5) - self.paramdict[btn_colour] = ID_PARAM_START + p_count - self.Bind(csel.EVT_COLOURSELECT, self.OnColorButton, btn_colour) -# if "none" in title: -# none_check = wx.CheckBox(which_panel, wx.ID_ANY, "Transparent") -# if p['value'] != '' and p['value'][0] == "none": -# none_check.SetValue(True) -# else: -# none_check.SetValue(False) -# none_check.SetFont( wx.Font( 12, wx.FONTFAMILY_DEFAULT, wx.NORMAL, text_style, 0, '')) -# which_sizer.Add(none_check, 0, wx.ADJUST_MINSIZE| wx.ALL, 5) -# self.paramdict[none_check] = ID_PARAM_START + p_count -# self.Bind(wx.EVT_CHECKBOX, self.EvtCheckBox, none_check) + none_check.SetValue(False) + none_check.SetFont( wx.Font( 12, wx.FONTFAMILY_DEFAULT, wx.NORMAL, text_style, 0, '')) + this_sizer.Add(none_check, 0, wx.ADJUST_MINSIZE| wx.ALL, 5) + which_sizer.Add( this_sizer ) + none_check.Bind(wx.EVT_CHECKBOX, self.OnColorChange) + p['wxId'].append( none_check.GetId() ) + else: + p['wxId'].append(None) if txt is not None: txt.SetFont( wx.Font( 12, wx.FONTFAMILY_DEFAULT, wx.NORMAL, text_style, 0, '')) - f_count = -1 for f in self.task.flags: - f_count += 1 which_sizer = self.tabsizer[ f['guisection'] ] which_panel = self.tab[ f['guisection'] ] title = text_beautify(f['description']) @@ -680,8 +681,8 @@ if 'value' in f: self.chk.SetValue(f['value']) self.chk.SetFont( wx.Font( 12, wx.FONTFAMILY_DEFAULT, wx.NORMAL, text_style, 0, '')) which_sizer.Add(self.chk, 0, wx.EXPAND| wx.ALL, 5) - self.paramdict[self.chk] = ID_FLAG_START + f_count - self.chk.Bind(wx.EVT_CHECKBOX, self.EvtCheckBox) + f['wxId'] = self.chk.GetId() + self.chk.Bind(wx.EVT_CHECKBOX, self.OnSetValue) maxsizes = (0,0) for section in sections: @@ -704,16 +705,24 @@ def OnPageChange(self, event): self.Layout() - def OnColorButton(self, event): - colorchooser = wx.FindWindowById( event.GetId() ) - new_color = colorchooser.GetValue()[:] - # This is weird: new_color is a 4-tuple and new_color[:] is a 3-tuple - # under wx2.8.1 - new_label = color_rgb2str.get( new_color, ':'.join(map(str,new_color)) ) - colorchooser.SetLabel( new_label ) - colorchooser.SetColour( new_color ) - colorchooser.Refresh() - self.getValues() + def OnColorChange( self, event ): + myId = event.GetId() + for p in self.task.params: + if type( p['wxId'] ) == type( [] ) and myId in p['wxId']: + has_button = p['wxId'][1] is not None + if has_button and wx.FindWindowById( p['wxId'][1] ).GetValue() == True: + p[ 'value' ] = 'none' + else: + colorchooser = wx.FindWindowById( p['wxId'][0] ) + new_color = colorchooser.GetValue()[:] + # This is weird: new_color is a 4-tuple and new_color[:] is a 3-tuple + # under wx2.8.1 + new_label = rgb2str.get( new_color, ':'.join(map(str,new_color)) ) + colorchooser.SetLabel( new_label ) + colorchooser.SetColour( new_color ) + colorchooser.Refresh() + p[ 'value' ] = colorchooser.GetLabel() + self.updateStatusLine() def updateStatusLine(self): """If we were part of a richer interface, report back the current command being built.""" @@ -723,56 +732,39 @@ except: pass - def getValues(self): - for (gui_object,param_num) in self.paramdict.items(): - if 'CheckBox' in str( gui_object ): - tasktype = self.task.flags - num = param_num-ID_FLAG_START - param_val = gui_object.GetValue() - else: - tasktype = self.task.params - num = param_num-ID_PARAM_START - if 'ColourSelect' in str( gui_object ): - if 'Select' in gui_object.GetLabel(): - param_val = '' - else: - data = gui_object.GetValue()[:] - param_val = color_rgb2str.get( data , ':'.join( map(str, data) ) ) - else: - param_val = gui_object.GetValue() - tasktype[num]['value'] = param_val - self.updateStatusLine() - - def EvtText(self, event): - self.getValues() - - def EvtCheckBox(self, event): - self.getValues() - - def EvtComboBox(self, event): - self.getValues() - - def EvtCheckBoxMulti(self, event): + def OnCheckBoxMulti(self, event): """Fill the values ,-separated string according to current status of the checkboxes.""" - theParamId = (event.GetId()-ID_MULTI_START ) / 20 - theCheckedId = (event.GetId()-ID_MULTI_START ) % 20 + me = event.GetId() + theParam = None + for p in self.task.params: + if type( p['wxId'] ) == type( [] ) and me in p['wxId']: + theParam = p + myIndex = p['wxId'].index( me ) # Unpack current value list currentValues={} - for isThere in self.task.params[theParamId]['value'].split(','): + for isThere in theParam['value'].split(','): currentValues[isThere] = 1 - theValue = self.task.params[theParamId]['values'][theCheckedId] + theValue = theParam['values'][myIndex] if event.Checked(): currentValues[ theValue ] = 1 else: del currentValues[ theValue ] currentValueList=[] # Keep the original order, so that some defaults may be recovered - for v in self.task.params[theParamId]['values']: + for v in theParam['values']: if currentValues.has_key(v): currentValueList.append( v ) # Pack it back - self.task.params[theParamId]['value'] = ','.join( currentValueList ) + theParam['value'] = ','.join( currentValueList ) self.updateStatusLine() + def OnSetValue(self, event): + myId = event.GetId() + me = wx.FindWindowById( myId ) + for porf in self.task.params + self.task.flags: + if type( porf[ 'wxId' ] ) == type( 1 ) and porf['wxId'] == myId: + porf[ 'value' ] = me.GetValue() + self.updateStatusLine() + def createCmd(self, ignoreErrors = False): """Produce a command line string for feeding into GRASS. @@ -798,13 +790,8 @@ self.OnError(errStr) return None - # create paramater dictionary to return to layer tree - dcmd_params['flags'] = self.task.flags - dcmd_params['params'] = self.task.params + return cmd - - return cmd,dcmd_params - def getInterfaceDescription( cmd ): """Returns the XML description for the GRASS cmd. @@ -855,7 +842,7 @@ self.parent = parentframe if len(cmdlst) > 1: - print "usage: %s " % cmdlst[0] + raise ValueError, "usage: %s " % cmdlst[0] else: # parse the interface decription self.grass_task = grassTask() From calvelo at grass.itc.it Thu Apr 12 01:44:11 2007 From: calvelo at grass.itc.it (calvelo@grass.itc.it) Date: Thu Apr 12 01:44:13 2007 Subject: [grass-addons] r474 - trunk/grassaddons/gui/gui_modules Message-ID: <200704112344.l3BNiBl6011021@grass.itc.it> Author: calvelo Date: 2007-04-12 01:44:03 +0200 (Thu, 12 Apr 2007) New Revision: 474 Modified: trunk/grassaddons/gui/gui_modules/menuform.py Log: - silly bugs solved Modified: trunk/grassaddons/gui/gui_modules/menuform.py =================================================================== --- trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-11 22:24:26 UTC (rev 473) +++ trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-11 23:44:03 UTC (rev 474) @@ -444,7 +444,7 @@ return cmd def OnRun(self, event): - cmd = self.createCmd()[0] + cmd = self.createCmd() if cmd != None and cmd[0:2] != "d.": # Send any non-display command to parent window (probably wxgui.py) @@ -475,11 +475,6 @@ self.SetStatusText("'%s' copied to clipboard" %\ (self.createCmd(ignoreErrors=True))) - def OnError(self, errMsg): - dlg = wx.MessageDialog(self, errMsg, "Error", wx.OK | wx.ICON_ERROR) - dlg.ShowModal() - dlg.Destroy() - def OnCancel(self, event): self.Destroy() @@ -792,6 +787,12 @@ return cmd + def OnError(self, errMsg): + dlg = wx.MessageDialog(self, errMsg, "Error", wx.OK | wx.ICON_ERROR) + dlg.ShowModal() + dlg.Destroy() + + def getInterfaceDescription( cmd ): """Returns the XML description for the GRASS cmd. From calvelo at grass.itc.it Thu Apr 12 03:28:30 2007 From: calvelo at grass.itc.it (calvelo@grass.itc.it) Date: Thu Apr 12 03:28:31 2007 Subject: [grass-addons] r475 - trunk/grassaddons/gui/gui_modules Message-ID: <200704120128.l3C1SU3W012049@grass.itc.it> Author: calvelo Date: 2007-04-12 03:28:24 +0200 (Thu, 12 Apr 2007) New Revision: 475 Modified: trunk/grassaddons/gui/gui_modules/menuform.py Log: - clean up: do not bind to self what doesn't need to Modified: trunk/grassaddons/gui/gui_modules/menuform.py =================================================================== --- trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-11 23:44:03 UTC (rev 474) +++ trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-12 01:28:24 UTC (rev 475) @@ -52,7 +52,6 @@ import os from os import system import gettext -#from gettext import _ gettext.install("wxgrass") sys.path.append(os.path.join(os.getenv("GISBASE"),"etc","wx")) @@ -139,7 +138,7 @@ "Make really long texts shorter" # TODO: remove magic number (calculate a correct value from # pixelSize of text and the magic number for maximum size - return escape_ampersand( os.linesep.join( textwrap.wrap( normalize_whitespace(someString), 72 ) ) ) + return escape_ampersand( os.linesep.join( textwrap.wrap( normalize_whitespace(someString), 70 ) ) ) def escape_ampersand(text): "Escapes ampersands with additional ampersand for GUI" @@ -305,8 +304,8 @@ GISBASE must be set in the environment to find the html docs dir. The SYNOPSIS section is skipped, since this Panel is supposed to be integrated into the cmdPanel.""" - def __init__(self, parent, id, grass_command = "index"): - wx.html.HtmlWindow.__init__(self, parent, id) + def __init__(self, grass_command = "index", *args, **kwargs): + wx.html.HtmlWindow.__init__(self, *args, **kwargs) self.fspath = os.getenv( "GISBASE" ) + "/docs/html/" self.SetStandardFonts( size = 11 ) self.fillContentsFromFile( self.fspath + grass_command + ".html" ) @@ -370,7 +369,7 @@ menuBar.Append(menu, "&File"); self.SetMenuBar(menuBar) - self.guisizer = wx.BoxSizer(wx.VERTICAL) + guisizer = wx.BoxSizer(wx.VERTICAL) # set apropriate output window # if self.parent: @@ -378,53 +377,52 @@ # self.goutput = self.parent.goutput # else: standalone=True - self.notebookpanel = cmdPanel( self, self.task, standalone) + self.notebookpanel = cmdPanel( parent=self, task=self.task, standalone=standalone ) if standalone: self.goutput = self.notebookpanel.goutput - self.guisizer.Add( self.notebookpanel, 1, flag = wx.EXPAND ) + guisizer.Add( self.notebookpanel, 1, flag = wx.EXPAND ) - status_text = "Enter parameters for " + self.task.name - if self.notebookpanel.tab.has_key('Main'): + status_text = _("Enter parameters for ") + self.task.name + if self.notebookpanel.hasMain: # We have to wait for the notebookpanel to be filled in order # to know if there actually is a Main tab - status_text += " (those of Main in bold typeface are required)" + status_text += _(" (those of Main in bold typeface are required)") self.SetStatusText( status_text ) btnsizer = wx.BoxSizer(wx.HORIZONTAL) - self.btn_cancel = wx.Button(self, wx.ID_CANCEL, "Cancel") - btnsizer.Add(self.btn_cancel, 0, wx.ALL| wx.ALIGN_CENTER, 10) + btn_cancel = wx.Button(self, wx.ID_CANCEL, _("Cancel") ) + btnsizer.Add( btn_cancel, 0, wx.ALL| wx.ALIGN_CENTER, 10) if self.get_dcmd is not None: # A callback has been set up - self.btn_apply = wx.Button(self, wx.ID_APPLY, "Apply") - btnsizer.Add(self.btn_apply, 0, wx.ALL| wx.ALIGN_CENTER, 10) - self.btn_ok = wx.Button(self, wx.ID_OK, "OK") - btnsizer.Add(self.btn_ok, 0, wx.ALL| wx.ALIGN_CENTER, 10) - self.btn_ok.SetDefault() - self.btn_apply.Bind(wx.EVT_BUTTON, self.OnApply) - self.btn_ok.Bind(wx.EVT_BUTTON, self.OnOK) + btn_apply = wx.Button(self, wx.ID_APPLY, _("Apply") ) + btnsizer.Add( btn_apply, 0, wx.ALL| wx.ALIGN_CENTER, 10) + btn_ok = wx.Button(self, wx.ID_OK, _("OK") ) + btnsizer.Add( btn_ok, 0, wx.ALL| wx.ALIGN_CENTER, 10) + btn_ok.SetDefault() + btn_apply.Bind(wx.EVT_BUTTON, self.OnApply) + btn_ok.Bind(wx.EVT_BUTTON, self.OnOK) else: # We're standalone - self.btn_run = wx.Button(self, wx.ID_OK, "Run") - btnsizer.Add(self.btn_run, 0, wx.ALL| wx.ALIGN_CENTER, 10) - self.btn_run.SetDefault() - self.btn_run.Bind(wx.EVT_BUTTON, self.OnRun) - self.btn_clipboard = wx.Button(self, wx.ID_OK, "Copy") - btnsizer.Add(self.btn_clipboard, 0, wx.ALL| wx.ALIGN_CENTER, 10) - self.btn_clipboard.Bind(wx.EVT_BUTTON, self.OnCopy) - self.guisizer.Add(btnsizer, 0, wx.ALIGN_BOTTOM) + btn_run = wx.Button(self, wx.ID_OK, _("Run") ) + btnsizer.Add( btn_run, 0, wx.ALL| wx.ALIGN_CENTER, 10) + btn_run.SetDefault() + btn_run.Bind(wx.EVT_BUTTON, self.OnRun) + btn_clipboard = wx.Button(self, wx.ID_OK, _("Copy") ) + btnsizer.Add(btn_clipboard, 0, wx.ALL| wx.ALIGN_CENTER, 10) + btn_clipboard.Bind(wx.EVT_BUTTON, self.OnCopy) + guisizer.Add(btnsizer, 0, wx.ALIGN_BOTTOM) wx.EVT_MENU(self, wx.ID_ABOUT, self.OnAbout) wx.EVT_MENU(self, ID_ABOUT_COMMAND, self.OnAboutCommand) wx.EVT_MENU(self, wx.ID_EXIT, self.OnCancel) - self.btn_cancel.Bind(wx.EVT_BUTTON, self.OnCancel) + btn_cancel.Bind(wx.EVT_BUTTON, self.OnCancel) self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) constrained_size = self.notebookpanel.GetSize() self.notebookpanel.SetSize( (constrained_size[0],constrained_size[1]+80) ) # 80 takes the tabbar into account - self.notebookpanel.SetSizer( self.notebookpanel.panelsizer ) self.notebookpanel.Layout() - self.guisizer.SetSizeHints(self) + guisizer.SetSizeHints(self) self.SetAutoLayout(True) - self.SetSizer(self.guisizer) + self.SetSizer(guisizer) self.Layout() @@ -507,7 +505,6 @@ wx.Panel.__init__( self, parent, *args, **kwargs ) self.task = task - self.selection = '' #selection from GIS element selector # Determine tab layout sections = ['Main'] @@ -527,45 +524,45 @@ if not there_is_main: sections = sections[1:] - self.panelsizer = wx.BoxSizer(wx.VERTICAL) + panelsizer = wx.BoxSizer(wx.VERTICAL) # Build notebook nbStyle=FN.FNB_NO_X_BUTTON|FN.FNB_NO_NAV_BUTTONS|FN.FNB_VC8|FN.FNB_BACKGROUND_GRADIENT - self.notebook = FN.FlatNotebook( self, id=wx.ID_ANY, style=nbStyle) - self.notebook.SetTabAreaColour(wx.Colour(125,200,175)) - self.notebook.Bind( FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnPageChange ) - self.tab = {} - self.tabsizer = {} + notebook = FN.FlatNotebook( self, id=wx.ID_ANY, style=nbStyle) + notebook.SetTabAreaColour(wx.Colour(125,200,175)) + notebook.Bind( FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnPageChange ) + tab = {} + tabsizer = {} for section in sections: - self.tab[section] = wx.ScrolledWindow(self.notebook, id = wx.ID_ANY ) - self.tab[section].SetScrollRate(10,10) - self.tabsizer[section] = wx.BoxSizer(wx.VERTICAL) - self.notebook.AddPage( self.tab[section], text = section ) + tab[section] = wx.ScrolledWindow( notebook ) + tab[section].SetScrollRate(10,10) + tabsizer[section] = wx.BoxSizer(wx.VERTICAL) + notebook.AddPage( tab[section], text = section ) # are we running from command line? if standalone: from gui_modules import wxgui_utils self.goutput = wxgui_utils.GMConsole(self) - self.outpage = self.notebook.AddPage(self.goutput, text="Command output") + self.outpage = notebook.AddPage(self.goutput, text=_("Command output") ) - manual_tab = helpPanel( self.notebook, id = wx.ID_ANY, grass_command = self.task.name) + manual_tab = helpPanel( parent = notebook, grass_command = self.task.name) if manual_tab.Ok: manual_tabsizer = wx.BoxSizer(wx.VERTICAL) - self.notebook.AddPage( manual_tab, text = "Manual" , select = False ) + notebook.AddPage( manual_tab, text = _("Manual") ) - self.notebook.SetSelection(0) - self.panelsizer.Add( self.notebook, 1, flag=wx.EXPAND ) + notebook.SetSelection(0) + panelsizer.Add( notebook, 1, flag=wx.EXPAND ) for p in self.task.params: - which_sizer = self.tabsizer[ p['guisection'] ] - which_panel = self.tab[ p['guisection'] ] + which_sizer = tabsizer[ p['guisection'] ] + which_panel = tab[ p['guisection'] ] title = text_beautify(p['description']) text_style = wx.FONTWEIGHT_BOLD txt = None if p['required'] == 'no': text_style = wx.FONTWEIGHT_NORMAL if p['multiple'] == 'yes' and len( p['values'] ) == 0: - title = "[multiple] " + title + title = _("[multiple]") + " " + title if p[ 'value'] == '' : p['value'] = p['default'] if (len(p['values']) > 0): @@ -585,29 +582,29 @@ p[ 'wxId' ].append( chkbox.GetId() ) if isDefault.has_key(val): chkbox.SetValue( True ) hSizer.Add( chkbox,0,wx.ADJUST_MINSIZE,5 ) - self.Bind(wx.EVT_CHECKBOX, self.OnCheckBoxMulti) + chkbox.Bind(wx.EVT_CHECKBOX, self.OnCheckBoxMulti) which_sizer.Add( hSizer, 0, wx.ADJUST_MINSIZE, 5) elif len(valuelist) == 1: txt = wx.StaticText(which_panel, label = title + '. Valid range=' + str(valuelist).strip("[]'") + ':' ) which_sizer.Add(txt, 0, wx.ADJUST_MINSIZE | wx.ALL, 5) - self.txt2 = wx.TextCtrl(which_panel, value = p['default'], + txt2 = wx.TextCtrl(which_panel, value = p['default'], size = (STRING_ENTRY_WIDTH, ENTRY_HEIGHT)) - if p['value'] != '': self.txt2.SetValue(p['value']) # parameter previously set + if p['value'] != '': txt2.SetValue(p['value']) # parameter previously set - which_sizer.Add(self.txt2, 0, wx.ADJUST_MINSIZE, 5) - p['wxId'] = self.txt2.GetId() - self.txt2.Bind(wx.EVT_TEXT, self.OnSetValue) + which_sizer.Add( txt2, 0, wx.ADJUST_MINSIZE, 5) + p['wxId'] = txt2.GetId() + txt2.Bind(wx.EVT_TEXT, self.OnSetValue) else: txt = wx.StaticText(which_panel, label = title + ':' ) which_sizer.Add(txt, 0, wx.ADJUST_MINSIZE | wx.ALL, 5) - self.cb = wx.ComboBox(which_panel, -1, p['default'], + cb = wx.ComboBox(which_panel, -1, p['default'], wx.Point(-1, -1), wx.Size(STRING_ENTRY_WIDTH, -1), valuelist, wx.CB_DROPDOWN) - if p['value'] != '': self.cb.SetValue(p['value']) # parameter previously set - which_sizer.Add(self.cb, 0, wx.ADJUST_MINSIZE, 5) - p['wxId'] = self.cb.GetId() - self.cb.Bind( wx.EVT_COMBOBOX, self.OnSetValue) + if p['value'] != '': cb.SetValue(p['value']) # parameter previously set + which_sizer.Add( cb, 0, wx.ADJUST_MINSIZE, 5) + p['wxId'] = cb.GetId() + cb.Bind( wx.EVT_COMBOBOX, self.OnSetValue) # text entry if (p['type'] in ('string','integer','float') @@ -618,24 +615,24 @@ txt = wx.StaticText(which_panel, label = title + ':' ) which_sizer.Add(txt, 0, wx.ADJUST_MINSIZE | wx.ALL, 5) - self.txt3 = wx.TextCtrl(which_panel, value = p['default'], + txt3 = wx.TextCtrl(which_panel, value = p['default'], size = (STRING_ENTRY_WIDTH, ENTRY_HEIGHT)) - if p['value'] != '': self.txt3.SetValue(p['value']) # parameter previously set - which_sizer.Add(self.txt3, 0, wx.ADJUST_MINSIZE| wx.ALL, 5) - p['wxId'] = self.txt3.GetId() - self.txt3.Bind(wx.EVT_TEXT, self.OnSetValue) + if p['value'] != '': txt3.SetValue(p['value']) # parameter previously set + which_sizer.Add( txt3, 0, wx.ADJUST_MINSIZE| wx.ALL, 5) + p['wxId'] = txt3.GetId() + txt3.Bind(wx.EVT_TEXT, self.OnSetValue) if p['type'] == 'string' and p['gisprompt'] == True: txt = wx.StaticText(which_panel, label = title + ':') which_sizer.Add(txt, 0, wx.ADJUST_MINSIZE | wx.ALL, 5) # element selection tree combobox (maps, icons, regions, etc.) if p['prompt'] != 'color': - self.selection = select.Select(which_panel, id=wx.ID_ANY, size=(300,-1), + selection = select.Select(which_panel, id=wx.ID_ANY, size=(300,-1), type=p['element']) - if p['value'] != '': self.selection.SetValue(p['value']) # parameter previously set - which_sizer.Add(self.selection, 0, wx.ADJUST_MINSIZE| wx.ALL, 5) - p['wxId'] = self.selection.GetId() - self.selection.Bind(wx.EVT_TEXT, self.OnSetValue) + if p['value'] != '': selection.SetValue(p['value']) # parameter previously set + which_sizer.Add( selection, 0, wx.ADJUST_MINSIZE| wx.ALL, 5) + p['wxId'] = selection.GetId() + selection.Bind(wx.EVT_TEXT, self.OnSetValue) # color entry elif p['prompt'] == 'color': if p['default'] != '': @@ -669,34 +666,37 @@ txt.SetFont( wx.Font( 12, wx.FONTFAMILY_DEFAULT, wx.NORMAL, text_style, 0, '')) for f in self.task.flags: - which_sizer = self.tabsizer[ f['guisection'] ] - which_panel = self.tab[ f['guisection'] ] + which_sizer = tabsizer[ f['guisection'] ] + which_panel = tab[ f['guisection'] ] title = text_beautify(f['description']) - self.chk = wx.CheckBox(which_panel,-1, label = title, style = wx.NO_BORDER) - if 'value' in f: self.chk.SetValue(f['value']) - self.chk.SetFont( wx.Font( 12, wx.FONTFAMILY_DEFAULT, wx.NORMAL, text_style, 0, '')) - which_sizer.Add(self.chk, 0, wx.EXPAND| wx.ALL, 5) - f['wxId'] = self.chk.GetId() - self.chk.Bind(wx.EVT_CHECKBOX, self.OnSetValue) + chk = wx.CheckBox(which_panel,-1, label = title, style = wx.NO_BORDER) + if 'value' in f: chk.SetValue(f['value']) + chk.SetFont( wx.Font( 12, wx.FONTFAMILY_DEFAULT, wx.NORMAL, text_style, 0, '')) + which_sizer.Add( chk, 0, wx.EXPAND| wx.ALL, 5) + f['wxId'] = chk.GetId() + chk.Bind(wx.EVT_CHECKBOX, self.OnSetValue) maxsizes = (0,0) for section in sections: - self.tabsizer[section].SetSizeHints( self.tab[section] ) - self.tabsizer[section].Fit( self.tab[section] ) - self.tab[section].SetAutoLayout(True) - self.tab[section].SetSizer( self.tabsizer[section] ) - self.tab[section].Layout() - minsecsizes = self.tabsizer[section].GetMinSize() + tabsizer[section].SetSizeHints( tab[section] ) + tabsizer[section].Fit( tab[section] ) + tab[section].SetAutoLayout(True) + tab[section].SetSizer( tabsizer[section] ) + tab[section].Layout() + minsecsizes = tabsizer[section].GetMinSize() maxsizes = map( lambda x: max( maxsizes[x], minsecsizes[x] ), (0,1) ) # TODO: be less arbitrary with these 600 constrained_size = (min(600, maxsizes[0]), min(600, maxsizes[1]) ) for section in sections: - self.tab[section].SetMinSize( constrained_size ) + tab[section].SetMinSize( constrained_size ) if manual_tab.Ok: manual_tab.SetMinSize( constrained_size ) + self.SetSizer( panelsizer ) + self.hasMain = tab.has_key( 'Main' ) # publish, to enclosing Frame for instance + def OnPageChange(self, event): self.Layout() From calvelo at grass.itc.it Thu Apr 12 07:06:04 2007 From: calvelo at grass.itc.it (calvelo@grass.itc.it) Date: Thu Apr 12 07:06:06 2007 Subject: [grass-addons] r476 - trunk/grassaddons/gui/gui_modules Message-ID: <200704120506.l3C564kY015030@grass.itc.it> Author: calvelo Date: 2007-04-12 07:05:55 +0200 (Thu, 12 Apr 2007) New Revision: 476 Modified: trunk/grassaddons/gui/gui_modules/menuform.py Log: - fixed GetId() for select.Select Modified: trunk/grassaddons/gui/gui_modules/menuform.py =================================================================== --- trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-12 01:28:24 UTC (rev 475) +++ trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-12 05:05:55 UTC (rev 476) @@ -74,10 +74,6 @@ reexec_with_pythonw() -ID_PARAM_START = 800 -ID_FLAG_START = 900 -ID_MULTI_START = 1000 - ID_ABOUT_COMMAND = 102 VSPACE = 4 @@ -631,7 +627,9 @@ type=p['element']) if p['value'] != '': selection.SetValue(p['value']) # parameter previously set which_sizer.Add( selection, 0, wx.ADJUST_MINSIZE| wx.ALL, 5) - p['wxId'] = selection.GetId() + # A select.Select is a combobox with two children: a textctl and a popupwindow; + # we target the textctl here + p['wxId'] = selection.GetChildren()[0].GetId() selection.Bind(wx.EVT_TEXT, self.OnSetValue) # color entry elif p['prompt'] == 'color': From calvelo at grass.itc.it Thu Apr 12 07:10:30 2007 From: calvelo at grass.itc.it (calvelo@grass.itc.it) Date: Thu Apr 12 07:10:31 2007 Subject: [grass-addons] r477 - trunk/grassaddons/gui/gui_modules Message-ID: <200704120510.l3C5AUhg015050@grass.itc.it> Author: calvelo Date: 2007-04-12 07:10:22 +0200 (Thu, 12 Apr 2007) New Revision: 477 Modified: trunk/grassaddons/gui/gui_modules/menuform.py Log: - added navigation for tabs Modified: trunk/grassaddons/gui/gui_modules/menuform.py =================================================================== --- trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-12 05:05:55 UTC (rev 476) +++ trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-12 05:10:22 UTC (rev 477) @@ -522,7 +522,7 @@ panelsizer = wx.BoxSizer(wx.VERTICAL) # Build notebook - nbStyle=FN.FNB_NO_X_BUTTON|FN.FNB_NO_NAV_BUTTONS|FN.FNB_VC8|FN.FNB_BACKGROUND_GRADIENT + nbStyle=FN.FNB_NO_X_BUTTON|FN.FNB_VC8|FN.FNB_BACKGROUND_GRADIENT notebook = FN.FlatNotebook( self, id=wx.ID_ANY, style=nbStyle) notebook.SetTabAreaColour(wx.Colour(125,200,175)) notebook.Bind( FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnPageChange ) From barton at grass.itc.it Thu Apr 12 08:59:40 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Thu Apr 12 08:59:41 2007 Subject: [grass-addons] r478 - trunk/grassaddons/gui/gui_modules Message-ID: <200704120659.l3C6xeMs016087@grass.itc.it> Author: barton Date: 2007-04-12 08:59:31 +0200 (Thu, 12 Apr 2007) New Revision: 478 Modified: trunk/grassaddons/gui/gui_modules/wxgui_utils.py Log: Non-display commands operate within computational region set by g.region rather than display region. Modified: trunk/grassaddons/gui/gui_modules/wxgui_utils.py =================================================================== --- trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-12 05:10:22 UTC (rev 477) +++ trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-12 06:59:31 UTC (rev 478) @@ -792,8 +792,18 @@ try: os.environ["GRASS_MESSAGE_FORMAT"] = "gui" self.cmd_output.write(cmd+"\n----------\n") + + # activate compuational region (set with g.region) for all non-display commands. + tmpreg = os.getenv("GRASS_REGION") + os.unsetenv("GRASS_REGION") + p = Popen(cmd +" --verbose", shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) + # deactivate computational region and return to display settings + if tmpreg: + os.environ["GRASS_REGION"] = tmpreg + + oline = p.stderr.readline() while oline: oline = oline.strip() @@ -869,8 +879,7 @@ Path to file name (string) or None """ - tempfile = os.popen("g.tempfile pid=%d" % - os.getpid()).readlines()[0].strip() + tempfile = os.popen("g.tempfile pid=%d" % os.getpid()).readlines()[0].strip() if not tempfile: return None From barton at grass.itc.it Thu Apr 12 09:00:30 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Thu Apr 12 09:00:31 2007 Subject: [grass-addons] r479 - trunk/grassaddons/gui/gui_modules Message-ID: <200704120700.l3C70Uoi016108@grass.itc.it> Author: barton Date: 2007-04-12 09:00:18 +0200 (Thu, 12 Apr 2007) New Revision: 479 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py trunk/grassaddons/gui/gui_modules/toolbars.py Log: Added zoom options menu. Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-12 06:59:31 UTC (rev 478) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-12 07:00:18 UTC (rev 479) @@ -36,6 +36,12 @@ from threading import Thread +try: + from subprocess import * +except: + from compat import subprocess + from compat.subprocess import * + gmpath = os.getenv("GISBASE") + "/etc/wx/gui_modules/" sys.path.append(gmpath) @@ -777,8 +783,12 @@ self.ZoomHistory(newreg['n'],newreg['s'],newreg['e'],newreg['w']) def ZoomBack(self): + """ + Zoom to previous extents in zoomhistory list + """ - if len(self.zoomhistory) > 0: + zoom = [] + if len(self.zoomhistory) > 1: self.zoomhistory.pop() zoom = self.zoomhistory[len(self.zoomhistory)-1] @@ -798,6 +808,135 @@ if len(self.zoomhistory) > 10: self.zoomhistory.pop(0) + def ZoomToMap(self, event): + """ + Set display extents to match selected raster + or vector map. + """ + + if not self.parent.gismanager: return + if not self.parent.gismanager.maptree.GetSelection(): return + layer = self.parent.gismanager.maptree.GetSelection() + type = self.parent.gismanager.maptree.layertype[layer] + dcmd = self.parent.gismanager.maptree.GetPyData(layer)[0] + mapname = None + for item in dcmd.split(' '): + if 'map=' in item: + mapname = item.split('=')[1] + + # selected layer must be a valid map + if type in ('raster', 'rgb', 'his'): + cmd = "r.info -g map=%s" % mapname + elif type in ('vector', 'thememap', 'themechart'): + cmd = "v.info -g map=%s" % mapname + else: + return + try: + p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) + + output = p.stdout.read().split('\n') + for oline in output: + extent = oline.split('=') + if extent[0] == 'north': + self.Map.region['n'] = float(extent[1]) + elif extent[0] == 'south': + self.Map.region['s'] = float(extent[1]) + elif extent[0] == 'east': + self.Map.region['e'] = float(extent[1]) + elif extent[0] == 'west': + self.Map.region['w'] = float(extent[1]) + + self.ZoomHistory(self.Map.region['n'],self.Map.region['s'],self.Map.region['e'],self.Map.region['w']) + self.UpdateMap() + + if p.stdout < 0: + print >> sys.stderr, "Child was terminated by signal", p.stdout + elif p.stdout > 0: + #print >> sys.stderr, p.stdout + pass + except OSError, e: + print >> sys.stderr, "Execution failed:", e + + def ZoomToWind(self, event): + """ + Set display geometry to match computational + region extents (set with g.region) + """ + + self.Map.region = self.Map.GetRegion() + self.Map.SetRegion() + self.ZoomHistory(self.Map.region['n'],self.Map.region['s'],self.Map.region['e'],self.Map.region['w']) + self.UpdateMap() + + def DisplayToWind(self, event): + """ + Set computational region (WIND file) to + match display extents + """ + tmpreg = os.getenv("GRASS_REGION") + os.unsetenv("GRASS_REGION") + + # get current resolution + grass_region = self.Map.GetRegion() + ewres = grass_region['ewres'] + nsres = grass_region['nsres'] + + # set extents to even increments of resolution + self.Map.region['n'] = round(self.Map.region['n']/nsres) * nsres + self.Map.region['s'] = round(self.Map.region['s']/nsres) * nsres + self.Map.region['e'] = round(self.Map.region['e']/nsres) * ewres + self.Map.region['w'] = round(self.Map.region['w']/nsres) * ewres + + cols = math.fabs(round(self.Map.region['n'] - self.Map.region['s'])) + rows = math.fabs(round(self.Map.region['e'] - self.Map.region['w'])) + + os.popen("g.region n=%d s=%d e=%d w=%d nsres=30.0 ewres=30.0" % ( + self.Map.region['n'], + self.Map.region['s'], + self.Map.region['e'], + self.Map.region['w']) ) + + if tmpreg: + os.environ["GRASS_REGION"] = tmpreg + + self.ZoomHistory(self.Map.region['n'],self.Map.region['s'],self.Map.region['e'],self.Map.region['w']) + self.UpdateMap() + + def ZoomToSaved(self, event): + """ + Set display geometry to match extents in + saved region file + """ + + print 'not yet functional' + pass + + def SaveDisplayRegion(self, event): + """ + Save display extents to named region file. + """ + + print 'not yet functional' + pass + +# tmpreg = os.getenv("GRASS_REGION") +# os.unsetenv("GRASS_REGION") +# +# # set extents to even increments of resolution +# self.Map.region['n'] = round(self.Map.region['n']/self.Map.region['nsres']) * self.Map.region['nsres'] +# self.Map.region['s'] = round(self.Map.region['s']/self.Map.region['nsres']) * self.Map.region['nsres'] +# self.Map.region['e'] = round(self.Map.region['e']/self.Map.region['ewres']) * self.Map.region['ewres'] +# self.Map.region['w'] = round(self.Map.region['w']/self.Map.region['ewres']) * self.Map.region['ewres'] +# +# os.popen("g.region n=%d s=%d e=%d w=%d" % ( +# self.Map.region['n'], +# self.Map.region['s'], +# self.Map.region['e'], +# self.Map.region['w']) ) +# +# if tmpreg: +# os.environ["GRASS_REGION"] = tmpreg + class MapFrame(wx.Frame): """ Main frame for map display window. Drawing takes place in child double buffered @@ -1340,6 +1479,59 @@ self.Map.changeOverlay(type=type, command=dcmd, l_active=True, l_render=False) self.params[type] = params + def onZoomMenu(self, event): + """ + Decorations overlay menu" + """ + point = wx.GetMousePosition() + zoommenu = wx.Menu() + # Add items to the menu + zoommap = wx.MenuItem(zoommenu, -1,'Zoom to selected map') +# bmp = wx.Image(os.path.join(icons,'module-d.barscale.gif'), wx.BITMAP_TYPE_GIF) +# bmp.Rescale(16, 16) +# bmp = bmp.ConvertToBitmap() +# zoommap.SetBitmap(bmp) + zoommenu.AppendItem(zoommap) + self.Bind(wx.EVT_MENU, self.MapWindow.ZoomToMap, zoommap) + + zoomwind = wx.MenuItem(zoommenu, -1,'Zoom to computational region (set with g.region)') +# bmp = wx.Image(os.path.join(icons,'module-d.barscale.gif'), wx.BITMAP_TYPE_GIF) +# bmp.Rescale(16, 16) +# bmp = bmp.ConvertToBitmap() +# zoomwind.SetBitmap(bmp) + zoommenu.AppendItem(zoomwind) + self.Bind(wx.EVT_MENU, self.MapWindow.ZoomToWind, zoomwind) + + savewind = wx.MenuItem(zoommenu, -1,'Set computational region from display') +# bmp = wx.Image(os.path.join(icons,'module-d.legend.gif'), wx.BITMAP_TYPE_GIF) +# bmp.Rescale(16, 16) +# bmp = bmp.ConvertToBitmap() +# savewind.SetBitmap(bmp) + zoommenu.AppendItem(savewind) + self.Bind(wx.EVT_MENU, self.MapWindow.DisplayToWind, savewind) + + zoomsaved = wx.MenuItem(zoommenu, -1,'Zoom to saved region') +# bmp = wx.Image(os.path.join(icons,'gui-font.gif'), wx.BITMAP_TYPE_GIF) +# bmp.Rescale(16, 16) +# bmp = bmp.ConvertToBitmap() +# zoomsaved.SetBitmap(bmp) + zoommenu.AppendItem(zoomsaved) + self.Bind(wx.EVT_MENU, self.MapWindow.ZoomToSaved, zoomsaved) + + savezoom = wx.MenuItem(zoommenu, -1,'Save display geometry to named region') +# bmp = wx.Image(os.path.join(icons,'gui-font.gif'), wx.BITMAP_TYPE_GIF) +# bmp.Rescale(16, 16) +# bmp = bmp.ConvertToBitmap() +# savezoom.SetBitmap(bmp) + zoommenu.AppendItem(savezoom) + self.Bind(wx.EVT_MENU, self.MapWindow.SaveDisplayRegion, savezoom) + + # Popup the menu. If an item is selected then its handler + # will be called before PopupMenu returns. + self.PopupMenu(zoommenu) + zoommenu.Destroy() + + # end of class MapFrame class DecDialog(wx.Dialog): Modified: trunk/grassaddons/gui/gui_modules/toolbars.py =================================================================== --- trunk/grassaddons/gui/gui_modules/toolbars.py 2007-04-12 06:59:31 UTC (rev 478) +++ trunk/grassaddons/gui/gui_modules/toolbars.py 2007-04-12 07:00:18 UTC (rev 479) @@ -59,11 +59,6 @@ wx.BITMAP_TYPE_ANY), bmpDisabled=wx.NullBitmap, kind=wx.ITEM_RADIO, shortHelp="Zoom out", longHelp="Drag or click mouse to unzoom") - self.zoomback = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="zoom_back", - bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"gui-zoom_back.gif"), - wx.BITMAP_TYPE_ANY), - bmpDisabled=wx.NullBitmap, kind=wx.ITEM_NORMAL, - shortHelp="Zoom back", longHelp="Zoom to previous display region") self.pan = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="pan", bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"gui-pan.gif"), wx.BITMAP_TYPE_ANY), @@ -77,6 +72,19 @@ self.toolbar.AddSeparator() + self.zoomback = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="zoom_back", + bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"gui-zoom_back.gif"), + wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, kind=wx.ITEM_NORMAL, + shortHelp="Zoom options", longHelp="Display zoom management") + self.zoommenu = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="zoommenu", + bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"gui-mapzoom.gif"), + wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, + shortHelp="Decoration", longHelp="Add graphic overlays to map") + self.toolbar.AddSeparator() + + self.dec = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="dec", bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"module-d.barscale.gif"), wx.BITMAP_TYPE_ANY), @@ -122,6 +130,7 @@ self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnPan, self.pan) self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnZoomBack, self.zoomback) self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.onDecoration, self.dec) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.onZoomMenu, self.zoommenu) self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnQuery, self.query) self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnErase, self.erase) self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.SaveToFile, self.savefile) From barton at grass.itc.it Thu Apr 12 09:01:52 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Thu Apr 12 09:01:53 2007 Subject: [grass-addons] r480 - trunk/grassaddons/gui/gui_modules Message-ID: <200704120701.l3C71qSp016138@grass.itc.it> Author: barton Date: 2007-04-12 09:01:42 +0200 (Thu, 12 Apr 2007) New Revision: 480 Modified: trunk/grassaddons/gui/gui_modules/render.py Log: Code cleanup Modified: trunk/grassaddons/gui/gui_modules/render.py =================================================================== --- trunk/grassaddons/gui/gui_modules/render.py 2007-04-12 07:00:18 UTC (rev 479) +++ trunk/grassaddons/gui/gui_modules/render.py 2007-04-12 07:01:42 UTC (rev 480) @@ -437,9 +437,9 @@ reg = reg.strip() key, val = reg.split("=",1) try: - region[key] = float(val) - except ValueError: - region[key] = val + region[key] = float(val) + except ValueError: + region[key] = val if tmpreg: os.environ["GRASS_REGION"] = tmpreg From chemin at grass.itc.it Thu Apr 12 18:30:54 2007 From: chemin at grass.itc.it (chemin@grass.itc.it) Date: Thu Apr 12 18:30:55 2007 Subject: [grass-addons] r481 - in trunk/grassaddons/gipe: r.albedo r.biomass r.dn2full.l7 r.dn2potrad.l7 r.dn2ref.ast r.dn2ref.l7 r.eb.deltat r.eb.disp r.eb.eta r.eb.evapfr r.eb.g0 r.eb.h0 r.eb.h_iter r.eb.molength r.eb.netrad r.eb.psi r.eb.rah r.eb.ublend r.eb.ustar r.eb.z0m r.emissivity r.evapo.PM r.evapo.PT r.evapo.TSA r.evapo.potrad r.latitude r.sattime r.sunhours r.vi r.vi.mpi Message-ID: <200704121630.l3CGUsG5023880@grass.itc.it> Author: chemin Date: 2007-04-12 18:29:55 +0200 (Thu, 12 Apr 2007) New Revision: 481 Modified: trunk/grassaddons/gipe/r.albedo/Makefile trunk/grassaddons/gipe/r.biomass/Makefile trunk/grassaddons/gipe/r.dn2full.l7/Makefile trunk/grassaddons/gipe/r.dn2potrad.l7/Makefile trunk/grassaddons/gipe/r.dn2ref.ast/Makefile trunk/grassaddons/gipe/r.dn2ref.l7/Makefile trunk/grassaddons/gipe/r.eb.deltat/Makefile trunk/grassaddons/gipe/r.eb.disp/Makefile trunk/grassaddons/gipe/r.eb.eta/Makefile trunk/grassaddons/gipe/r.eb.evapfr/Makefile trunk/grassaddons/gipe/r.eb.g0/Makefile trunk/grassaddons/gipe/r.eb.h0/Makefile trunk/grassaddons/gipe/r.eb.h_iter/Makefile trunk/grassaddons/gipe/r.eb.molength/Makefile trunk/grassaddons/gipe/r.eb.netrad/Makefile trunk/grassaddons/gipe/r.eb.psi/Makefile trunk/grassaddons/gipe/r.eb.rah/Makefile trunk/grassaddons/gipe/r.eb.ublend/Makefile trunk/grassaddons/gipe/r.eb.ustar/Makefile trunk/grassaddons/gipe/r.eb.z0m/Makefile trunk/grassaddons/gipe/r.emissivity/Makefile trunk/grassaddons/gipe/r.evapo.PM/Makefile trunk/grassaddons/gipe/r.evapo.PT/Makefile trunk/grassaddons/gipe/r.evapo.TSA/Makefile trunk/grassaddons/gipe/r.evapo.potrad/Makefile trunk/grassaddons/gipe/r.latitude/Makefile trunk/grassaddons/gipe/r.sattime/Makefile trunk/grassaddons/gipe/r.sunhours/Makefile trunk/grassaddons/gipe/r.vi.mpi/Makefile trunk/grassaddons/gipe/r.vi/Makefile Log: Added Large file support in Makefile to all modules but i.atcorr Modified: trunk/grassaddons/gipe/r.albedo/Makefile =================================================================== --- trunk/grassaddons/gipe/r.albedo/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.albedo/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -7,4 +7,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.biomass/Makefile =================================================================== --- trunk/grassaddons/gipe/r.biomass/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.biomass/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -7,4 +7,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.dn2full.l7/Makefile =================================================================== --- trunk/grassaddons/gipe/r.dn2full.l7/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.dn2full.l7/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -7,4 +7,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.dn2potrad.l7/Makefile =================================================================== --- trunk/grassaddons/gipe/r.dn2potrad.l7/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.dn2potrad.l7/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -8,4 +8,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.dn2ref.ast/Makefile =================================================================== --- trunk/grassaddons/gipe/r.dn2ref.ast/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.dn2ref.ast/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -7,4 +7,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.dn2ref.l7/Makefile =================================================================== --- trunk/grassaddons/gipe/r.dn2ref.l7/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.dn2ref.l7/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -7,4 +7,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.eb.deltat/Makefile =================================================================== --- trunk/grassaddons/gipe/r.eb.deltat/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.eb.deltat/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -7,4 +7,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.eb.disp/Makefile =================================================================== --- trunk/grassaddons/gipe/r.eb.disp/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.eb.disp/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -7,4 +7,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.eb.eta/Makefile =================================================================== --- trunk/grassaddons/gipe/r.eb.eta/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.eb.eta/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -7,4 +7,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.eb.evapfr/Makefile =================================================================== --- trunk/grassaddons/gipe/r.eb.evapfr/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.eb.evapfr/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -7,4 +7,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.eb.g0/Makefile =================================================================== --- trunk/grassaddons/gipe/r.eb.g0/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.eb.g0/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -7,4 +7,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.eb.h0/Makefile =================================================================== --- trunk/grassaddons/gipe/r.eb.h0/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.eb.h0/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -7,4 +7,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.eb.h_iter/Makefile =================================================================== --- trunk/grassaddons/gipe/r.eb.h_iter/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.eb.h_iter/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -7,4 +7,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.eb.molength/Makefile =================================================================== --- trunk/grassaddons/gipe/r.eb.molength/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.eb.molength/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -7,4 +7,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.eb.netrad/Makefile =================================================================== --- trunk/grassaddons/gipe/r.eb.netrad/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.eb.netrad/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -7,4 +7,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.eb.psi/Makefile =================================================================== --- trunk/grassaddons/gipe/r.eb.psi/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.eb.psi/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -7,4 +7,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.eb.rah/Makefile =================================================================== --- trunk/grassaddons/gipe/r.eb.rah/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.eb.rah/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -7,4 +7,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.eb.ublend/Makefile =================================================================== --- trunk/grassaddons/gipe/r.eb.ublend/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.eb.ublend/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -7,4 +7,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.eb.ustar/Makefile =================================================================== --- trunk/grassaddons/gipe/r.eb.ustar/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.eb.ustar/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -7,4 +7,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.eb.z0m/Makefile =================================================================== --- trunk/grassaddons/gipe/r.eb.z0m/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.eb.z0m/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -7,4 +7,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.emissivity/Makefile =================================================================== --- trunk/grassaddons/gipe/r.emissivity/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.emissivity/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -7,4 +7,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.evapo.PM/Makefile =================================================================== --- trunk/grassaddons/gipe/r.evapo.PM/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.evapo.PM/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -9,4 +9,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.evapo.PT/Makefile =================================================================== --- trunk/grassaddons/gipe/r.evapo.PT/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.evapo.PT/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -9,4 +9,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.evapo.TSA/Makefile =================================================================== --- trunk/grassaddons/gipe/r.evapo.TSA/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.evapo.TSA/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -9,4 +9,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.evapo.potrad/Makefile =================================================================== --- trunk/grassaddons/gipe/r.evapo.potrad/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.evapo.potrad/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -7,4 +7,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.latitude/Makefile =================================================================== --- trunk/grassaddons/gipe/r.latitude/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.latitude/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -8,4 +8,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.sattime/Makefile =================================================================== --- trunk/grassaddons/gipe/r.sattime/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.sattime/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -8,4 +8,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.sunhours/Makefile =================================================================== --- trunk/grassaddons/gipe/r.sunhours/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.sunhours/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -8,4 +8,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.vi/Makefile =================================================================== --- trunk/grassaddons/gipe/r.vi/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.vi/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -7,4 +7,8 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd Modified: trunk/grassaddons/gipe/r.vi.mpi/Makefile =================================================================== --- trunk/grassaddons/gipe/r.vi.mpi/Makefile 2007-04-12 07:01:42 UTC (rev 480) +++ trunk/grassaddons/gipe/r.vi.mpi/Makefile 2007-04-12 16:29:55 UTC (rev 481) @@ -7,5 +7,9 @@ include $(MODULE_TOPDIR)/include/Make/Module.make +ifneq ($(USE_LARGEFILES),) + EXTRA_CFLAGS = -D_FILE_OFFSET_BITS=64 +endif + default: cmd CC=mpicc From chemin at grass.itc.it Thu Apr 12 18:33:40 2007 From: chemin at grass.itc.it (chemin@grass.itc.it) Date: Thu Apr 12 18:33:41 2007 Subject: [grass-addons] r482 - in trunk/grassaddons/gipe: . screenshots Message-ID: <200704121633.l3CGXeNq023902@grass.itc.it> Author: chemin Date: 2007-04-12 18:33:26 +0200 (Thu, 12 Apr 2007) New Revision: 482 Added: trunk/grassaddons/gipe/screenshots/ trunk/grassaddons/gipe/screenshots/gipe01.png trunk/grassaddons/gipe/screenshots/gipe02.png trunk/grassaddons/gipe/screenshots/gipe03.png trunk/grassaddons/gipe/screenshots/gipe04.png trunk/grassaddons/gipe/screenshots/gipe05.png trunk/grassaddons/gipe/screenshots/gipe06.png trunk/grassaddons/gipe/screenshots/gipe07.png Log: Added screenshots of the main GUI and main modules GUI Added: trunk/grassaddons/gipe/screenshots/gipe01.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/screenshots/gipe01.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/screenshots/gipe02.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/screenshots/gipe02.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/screenshots/gipe03.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/screenshots/gipe03.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/screenshots/gipe04.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/screenshots/gipe04.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/screenshots/gipe05.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/screenshots/gipe05.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/screenshots/gipe06.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/screenshots/gipe06.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/screenshots/gipe07.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/screenshots/gipe07.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream From landa at grass.itc.it Fri Apr 13 11:15:05 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Fri Apr 13 11:15:07 2007 Subject: [grass-addons] r483 - trunk/grassaddons/gui/gui_modules Message-ID: <200704130915.l3D9F53W004371@grass.itc.it> Author: landa Date: 2007-04-13 11:15:05 +0200 (Fri, 13 Apr 2007) New Revision: 483 Modified: trunk/grassaddons/gui/gui_modules/cmd.py Log: * Popen class used instead of popen3 fn * header added * Command class rearranged Modified: trunk/grassaddons/gui/gui_modules/cmd.py =================================================================== --- trunk/grassaddons/gui/gui_modules/cmd.py 2007-04-12 16:33:26 UTC (rev 482) +++ trunk/grassaddons/gui/gui_modules/cmd.py 2007-04-13 09:15:05 UTC (rev 483) @@ -1,101 +1,172 @@ +""" +PACKAGE: digit + +CLASSES: + * EndOfCommand + * Command + +PURPOSE: Command interface + +AUTHORS: The GRASS Development Team + Jachym Cepicky, Martin Landa + +COPYRIGHT: (C) 2007 by the GRASS Development Team + This program is free software under the GNU General Public + License (>=v2). Read the file COPYING that comes with GRASS + for details. +""" + +import os +try: + import subprocess +except: + import combat.subprocess as subprocess + import grassenv -import os + class EndOfCommand(Exception): + """ + End of command indicator + """ def __str__(self): return "End of command" class Command: - def __init__ (self,cmd,stdin=None,verbose=False): - self.module_stdout = None + """ + Run command on the background + + Usage: + cmd = Command(cmd="d.rast elevation.dem", verbose=True, wait=True) + + if cmd.returncode == None: + print "RUNNING" + elif cmd.returncode == 0: + print "SUCCESS" + else: + print "FAILURE (%d)" % cmd.returncode + + for msg in cmd.module_msg: + if msg[0] == "GRASS_INFO_PERCENT": + print "Percent done: %d" % (int(msg[1])) + else: + print "General message:", msg[1] + """ + def __init__ (self, cmd, stdin=None, verbose=False, wait=True): + # input self.module_stdin = None - self.cmd = cmd - self.line = None + self.cmd = cmd + self.module = None + + # output + self.module_stderr = None + self.module_msg = [] # list of messages (msgtype, content) os.environ["GRASS_MESSAGE_FORMAT"] = "gui" - (self.module_stdin, self.module_stdout, self.module_stderr) = os.popen3(self.cmd) + # run command + if 0: + (self.module_stdin, self.module_stdout, self.module_stderr) = \ + os.popen3(self.cmd) + else: + self.module = subprocess.Popen(self.cmd, shell=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) + os.environ["GRASS_MESSAGE_FORMAT"] = "text" - + + if self.module: + self.module_stdin = self.module.stdin + self.module_stderr = self.module.stderr + if stdin: self.module_stdin.write(stdin) self.module_stdin.close() - if not verbose: - self.RunS() + + try: + self.Run(verbose) + except EndOfCommand: + pass - def Run(self): - """ - run command verbosely - - Returns: (msgtype, content) + if self.module: + if wait: + self.module.wait() + self.returncode = self.module.returncode + else: + self.returncode = None - Usage: - cmd = Command("d.rast elevation.dem") - try: - (msgtype,content) = cmd.RunV() - while 1: - if msgtype == "GRASS_INFO_PERCENT": - print "Percent done: %d" % (int(content)) - else: - print "General message:", content - (msgtype,content) = cmd.RunV() - except EndOfCommand: - print "end" + + def Run(self, verbose=False): """ + Process messages read from stderr + """ msgtype = None - cont = None - line = None + content = None + line = None - line = self.module_stderr.readline() while 1: - if not line: - raise EndOfCommand() + line = self.module_stderr.readline() + if not line or line.find("GRASS_INFO_END") > -1: + raise EndOfCommand if line.find(':') > -1: - break - line = self.module_stderr.readline() - msgtype, cont = line.split(":") + msgtype, content = line.split(":", 1) + if verbose: + self.module_msg.append((msgtype, content.strip())) + else: # write only fatal errors and warnigs + if msgtype.find("GRASS_INFO_ERROR") > -1 or \ + msgtype.find("GRASS_INFO_WARNING") > -1: + self.module_msg.append((msgtype, content.strip())) - cont=cont.strip() - return (msgtype,cont.strip()) + return - def RunS(self): - """ - run command silently +# testing ... +if __name__ == "__main__": + #print __doc__ + + # d.rast verbosely, wait for process termination + print "Running d.rast..." + + cmd = Command(cmd="d.rast elevation.dem", verbose=True, wait=True) + + if cmd.returncode == None: + print "RUNNING" + elif cmd.returncode == 0: + print "SUCCESS" + else: + print "FAILURE (%d)" % cmd.returncode - Returns: - None if OK - Usage: - cmd = Command("d.rast elevation.dem") - if cmd.RunS(): - print "ERRRROR" + for msg in cmd.module_msg: + if msg[0] == "GRASS_INFO_PERCENT": + print "Percent done: %d" % (int(msg[1])) + else: + print "General message:", msg[1] + + # v.net.path silently, wait for process termination + print "Running v.net.path for 0 593527.6875 4925297.0625 602083.875 4917545.8125..." - FIXME: maybe use os.system instead? - """ + cmd = Command(cmd="v.net.path in=roads@PERMANENT out=tmp --o", + stdin="0 593527.6875 4925297.0625 602083.875 4917545.8125", + verbose=False, + wait=True) - line = self.module_stderr.readline() - while 1: - if not line: - break - line =self.module_stderr.readline() - return + if cmd.returncode == None: + print "RUNNING" + elif cmd.returncode == 0: + print "SUCCESS" + else: + print "FAILURE (%d)" % cmd.returncode -if __name__ == "__main__": - print "Running d.rast" - cmd=Command("d.rast elevation.dem") - try: - (msgtype,content) = cmd.RunV() - while 1: - if msgtype == "GRASS_INFO_PERCENT": - print "Percent done: %d" % (int(content)) - else: - print "General message:", content - (msgtype,content) = cmd.RunV() - except EndOfCommand: - print "konec" + # d.vect silently, do not wait for process termination + # returncode will be None + print "Running d.vect tmp..." - print "Running v.net.path for 0 593527.6875 4925297.0625 602083.875 4917545.8125" - cmd=Command("v.net.path in=roads out=tmp --o", "0 593527.6875 4925297.0625 602083.875 4917545.8125") - cmd.RunS() - print "Running d.vect tmp" - cmd = Command("d.vect tmp") - cmd.RunS() + cmd = Command("d.vect tmp", verbose=False, wait=False) + + if cmd.returncode == None: + print "RUNNING" + elif cmd.returncode == 0: + print "SUCCESS" + else: + print "FAILURE (%d)" % cmd.returncode From landa at grass.itc.it Fri Apr 13 11:16:15 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Fri Apr 13 11:16:17 2007 Subject: [grass-addons] r484 - trunk/grassaddons/gui/gui_modules Message-ID: <200704130916.l3D9GFoW004398@grass.itc.it> Author: landa Date: 2007-04-13 11:16:15 +0200 (Fri, 13 Apr 2007) New Revision: 484 Added: trunk/grassaddons/gui/gui_modules/digit.py Modified: trunk/grassaddons/gui/gui_modules/__init__.py Log: digit package added Modified: trunk/grassaddons/gui/gui_modules/__init__.py =================================================================== --- trunk/grassaddons/gui/gui_modules/__init__.py 2007-04-13 09:15:05 UTC (rev 483) +++ trunk/grassaddons/gui/gui_modules/__init__.py 2007-04-13 09:16:15 UTC (rev 484) @@ -4,4 +4,5 @@ "menudata", "menuform", "select", + "digit", ] Added: trunk/grassaddons/gui/gui_modules/digit.py =================================================================== --- trunk/grassaddons/gui/gui_modules/digit.py (rev 0) +++ trunk/grassaddons/gui/gui_modules/digit.py 2007-04-13 09:16:15 UTC (rev 484) @@ -0,0 +1,72 @@ +""" +PACKAGE: digit + +CLASSES: + * VEdit + * VDigit + +PURPOSE: Digitization tool wxPython GUI prototype + + Note: Initial version under development + + Progress: + (1) v.edit called on the background (class VEdit) + (2) Reimplentation of v.digit (VDigit) + +AUTHORS: The GRASS Development Team + Martin Landa + +COPYRIGHT: (C) 2007 by the GRASS Development Team + This program is free software under the GNU General Public + License (>=v2). Read the file COPYING that comes with GRASS + for details. +""" + +import cmd + +# +class Digit: + """ + Abstract digitization class + """ + pass + +# +class VEdit(Digit): + """ + Prototype of digitization class based on v.edit command + """ + def AddPoint (self, map, x, y): + """ + Add point to the vector map + """ + addstring="""P 1 + %f %f""" % (x,y) + command = """v.edit -n map=%s tool=add""" % (map) + + # run the command + vedit = cmd.Command(cmd=command, stdin=addstring) + + # result? + if vedit.returncode == 0: + pass + else: + print "FAILURE" + for msg in vedit.msg: + print msg[1] + +# +class VDigit(Digit): + """ + Prototype of digitization class based on v.digit reimplementation + + Under development (wxWidgets C/C++ background) + """ + pass + + +############################## +# digitization class instance +############################## + +Digit = VEdit() From landa at grass.itc.it Fri Apr 13 11:20:07 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Fri Apr 13 11:20:08 2007 Subject: [grass-addons] r485 - trunk/grassaddons/gui/gui_modules Message-ID: <200704130920.l3D9K7tO004446@grass.itc.it> Author: landa Date: 2007-04-13 11:20:07 +0200 (Fri, 13 Apr 2007) New Revision: 485 Modified: trunk/grassaddons/gui/gui_modules/render.py Log: GetListofLayers(): return always 'selected' list Modified: trunk/grassaddons/gui/gui_modules/render.py =================================================================== --- trunk/grassaddons/gui/gui_modules/render.py 2007-04-13 09:16:15 UTC (rev 484) +++ trunk/grassaddons/gui/gui_modules/render.py 2007-04-13 09:20:07 UTC (rev 485) @@ -546,11 +546,8 @@ else: selected.append(layer) - if len(selected) > 0: - return selected - else: - return None - + return selected + def Render(self, force=False): """ Creates final image composite From landa at grass.itc.it Fri Apr 13 11:29:29 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Fri Apr 13 11:29:31 2007 Subject: [grass-addons] r486 - trunk/grassaddons/gui/gui_modules Message-ID: <200704130929.l3D9TTn9004509@grass.itc.it> Author: landa Date: 2007-04-13 11:29:29 +0200 (Fri, 13 Apr 2007) New Revision: 486 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py trunk/grassaddons/gui/gui_modules/toolbars.py Log: various minor (older) changes (almost digit stuff) Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-13 09:20:07 UTC (rev 485) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-13 09:29:29 UTC (rev 486) @@ -29,6 +29,7 @@ import grassenv import track import menuform +from digit import Digit as Digit import images imagepath = images.__path__[0] @@ -650,9 +651,11 @@ # digitizing elif self.parent.digittoolbar: - if self.parent.digittoolbar.digitize == "point": + if self.parent.digittoolbar.action == "addpoint": + #self.SetCursor (self.parent.cursors["cross"]) east,north= self.Pixel2Cell(self.mouse['begin'][0],self.mouse['begin'][1]) - self.parent.digittoolbar.AddPoint(east,north) + Digit.AddPoint(self.parent.digittoolbar.layers[self.parent.digittoolbar.layerID], + east,north) # redraw map self.render=True self.UpdateMap() @@ -1016,7 +1019,9 @@ # Init map display # self.InitDisplay() # initialize region values -# self.MapWindow = DrawWindow(self) # initialize buffered DC + + # initialize buffered DC + # self.MapWindow = DrawWindow(self) self.MapWindow = BufferedWindow(self, id = wx.ID_ANY) # initialize buffered DC self.MapWindow.Bind(wx.EVT_MOTION, self.OnMotion) @@ -1063,19 +1068,42 @@ """ if name == "map": self.maptoolbar = toolbars.MapToolbar(self, self.Map) - self._mgr.AddPane(self.maptoolbar.toolbar, wx.aui.AuiPaneInfo(). - Name("maptoolbar").Caption("Map Toolbar"). - ToolbarPane().Top().LeftDockable(False).RightDockable(False). - BottomDockable(True).CloseButton(False)) - if name == "digit": - self.digittoolbar = toolbars.DigitToolbar(self,self.Map) + self._mgr.AddPane(self.maptoolbar.toolbar, + wx.aui.AuiPaneInfo(). + Name("maptoolbar").Caption("Map Toolbar"). + ToolbarPane().Top().LeftDockable(True).RightDockable(False). + BottomDockable(False).TopDockable(False).CloseButton(False)) + + elif name == "digit": + self.digittoolbar = toolbars.DigitToolbar(self, self.Map) + self._mgr.AddPane(self.digittoolbar.toolbar, wx.aui.AuiPaneInfo(). - Name("digittoolbar").Caption("Digit Toolbar"). - ToolbarPane().Top().LeftDockable(False).RightDockable(False). - BottomDockable(True).CloseButton(False)) + Name("digittoolbar").Caption("Digit Toolbar"). + ToolbarPane().Top().Row(1).LeftDockable(False).RightDockable(True). + BottomDockable(False).TopDockable(False).CloseButton(False)) + self._mgr.Update() + def RemoveToolbar (self, name): + """ + Removes toolbar from the window + + TODO: Only hide, activate by calling AddToolbar() + """ + + # cannot hide main toolbar + if name == "map": + return + elif name == "digit": + # TODO: not destroy only hide + self._mgr.DetachPane (self.digittoolbar.toolbar) + self.digittoolbar.toolbar.Destroy() + self.digittoolbar = None + + self.maptoolbar.combo.SetValue (""); + self._mgr.Update() + def InitDisplay(self): """ Initialize map display, set dimensions and map region Modified: trunk/grassaddons/gui/gui_modules/toolbars.py =================================================================== --- trunk/grassaddons/gui/gui_modules/toolbars.py 2007-04-13 09:20:07 UTC (rev 485) +++ trunk/grassaddons/gui/gui_modules/toolbars.py 2007-04-13 09:29:29 UTC (rev 486) @@ -3,7 +3,9 @@ class: * MapToolbar +* DigitToolbar """ + import wx import os import wxgui_utils @@ -19,22 +21,29 @@ """ Main Map Display toolbar """ + def __init__(self, mapdisplay, map): + global icons + self.mapcontent = map self.mapdisplay = mapdisplay - self.toolbar = wx.ToolBar(parent=self.mapdisplay, id=wx.ID_ANY, size=(5,100)) + self.toolbar = wx.ToolBar(parent=self.mapdisplay, id=wx.ID_ANY) + #self.SetToolBar(self.toolbar) - self.toolbar.SetToolBitmapSize(wx.Size(24,24)) + tsize = (24, 24) + self.toolbar.SetToolBitmapSize(tsize) + # # Draw # - self.displaymap = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="displaymap", - bitmap=wx.Bitmap(name=os.path.join(wxgui_utils.icons,"gui-display.gif"), - type=wx.BITMAP_TYPE_ANY), - bmpDisabled=wx.NullBitmap, kind=wx.ITEM_NORMAL, - shortHelp="Display map", longHelp="") + self.displaymap = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="displaymap", + bitmap=wx.Bitmap(name=os.path.join(wxgui_utils.icons, "gui-display.gif")), + bmpDisabled=wx.NullBitmap, + kind=wx.ITEM_NORMAL, + shortHelp="Display map", + longHelp="") self.erase = self.toolbar.AddLabelTool(wx.ID_ANY, "erase", wx.Bitmap(os.path.join(wxgui_utils.icons,"gui-erase.gif"), wx.BITMAP_TYPE_ANY), @@ -47,7 +56,8 @@ self.pointer = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="pointer", bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"gui-pointer.gif"), wx.BITMAP_TYPE_ANY), - bmpDisabled=wx.NullBitmap, kind=wx.ITEM_RADIO, + bmpDisabled=wx.NullBitmap, + kind=wx.ITEM_RADIO, shortHelp="Pointer", longHelp="") self.zoomin = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="zoom_in", bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"gui-zoom_in.gif"), @@ -57,18 +67,21 @@ self.zoomout = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="zoom_out", bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"gui-zoom_out.gif"), wx.BITMAP_TYPE_ANY), - bmpDisabled=wx.NullBitmap, kind=wx.ITEM_RADIO, + bmpDisabled=wx.NullBitmap, + kind=wx.ITEM_RADIO, shortHelp="Zoom out", longHelp="Drag or click mouse to unzoom") self.pan = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="pan", bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"gui-pan.gif"), wx.BITMAP_TYPE_ANY), - bmpDisabled=wx.NullBitmap, kind=wx.ITEM_RADIO, + bmpDisabled=wx.NullBitmap, + kind=wx.ITEM_RADIO, shortHelp="Pan", longHelp="Drag with mouse to pan") self.query = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="query", - bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"gui-query.gif"), + bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"gui-query.gif"), wx.BITMAP_TYPE_ANY), - bmpDisabled=wx.NullBitmap, kind=wx.ITEM_RADIO, - shortHelp="Query", longHelp="Query selected map") + bmpDisabled=wx.NullBitmap, + kind=wx.ITEM_RADIO, + shortHelp="Query", longHelp="Query selected map") self.toolbar.AddSeparator() @@ -89,7 +102,8 @@ bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"module-d.barscale.gif"), wx.BITMAP_TYPE_ANY), bmpDisabled=wx.NullBitmap, - shortHelp="Decoration", longHelp="Add graphic overlays to map") + shortHelp="Decoration", + longHelp="Add graphic overlays to map") self.toolbar.AddSeparator() @@ -100,9 +114,11 @@ #bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"file-save.gif"), #wx.BITMAP_TYPE_ANY), # just testing wx.ArtProvider - bitmap=wx.ArtProvider.GetBitmap(id=wx.ART_FILE_SAVE, client=wx.ART_BUTTON), - bmpDisabled=wx.NullBitmap, kind=wx.ITEM_NORMAL, - shortHelp="Save display to PNG file", longHelp="") + bitmap=wx.ArtProvider.GetBitmap(id=wx.ART_FILE_SAVE, client=wx.ART_BUTTON, size=tsize), + bmpDisabled=wx.NullBitmap, + kind=wx.ITEM_NORMAL, + shortHelp="Save display to PNG file", + longHelp="") self.printmap = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="printmap", #bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"file-save.gif"), @@ -117,25 +133,26 @@ # # Optional toolbars # - cb = wx.ComboBox(self.toolbar, id=wx.ID_ANY, value='', + self.combo = wx.ComboBox(self.toolbar, id=wx.ID_ANY, value='', choices=['Digitize'], size=(-1, -1), style=wx.CB_READONLY , name='Tools') - self.combo = self.toolbar.AddControl(cb) + self.comboid = self.toolbar.AddControl(self.combo) + self.toolbar.Realize() - self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.ReDraw, self.displaymap) - self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.Pointer, self.pointer) - self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnZoomIn, self.zoomin) - self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnZoomOut, self.zoomout) - self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnPan, self.pan) - self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnZoomBack, self.zoomback) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.ReDraw, self.displaymap) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.Pointer, self.pointer) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnZoomIn, self.zoomin) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnZoomOut, self.zoomout) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnPan, self.pan) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnZoomBack, self.zoomback) self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.onDecoration, self.dec) - self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.onZoomMenu, self.zoommenu) - self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnQuery, self.query) - self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnErase, self.erase) - self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.SaveToFile, self.savefile) - self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.PrintMap, self.printmap) - self.mapdisplay.Bind(wx.EVT_COMBOBOX, self.OnSelect, self.combo) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.onZoomMenu, self.zoommenu) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnQuery, self.query) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnErase, self.erase) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.SaveToFile, self.savefile) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.PrintMap, self.printmap) + self.mapdisplay.Bind(wx.EVT_COMBOBOX, self.OnSelect, self.comboid) def OnSelect(self,event): tool = event.GetString() @@ -144,89 +161,114 @@ self.mapdisplay.AddToolbar("digit") class DigitToolbar: - def __init__(self,parent,map): + """ + Toolbar for digitization + """ - global icons + def __init__(self, parent, map): self.mapcontent = map - self.digitize=None - self.parent=parent - self.toolbar = wx.ToolBar(self.parent, wx.ID_ANY) - icons = os.path.join(icons,"v.digit") + self.parent = parent + self.icons = os.path.join (icons, "v.digit") - self.addString = "" + # selected map to digitize + self.layerID = -1 + # action (digitize new point, line, etc. + self.action = None + # list of available vector maps + self.layers = self._getListOfLayers() - self.layers = self._getListOfLayers() + self.addString = "" + # create toolbar + self.toolbar = wx.ToolBar(parent=self.parent, id=wx.ID_ANY) + self.toolbar.SetToolBitmapSize(wx.Size(24,24)) + self.initToolbar() def initToolbar(self): - self.combo = wx.ComboBox(self.toolbar, 4, 'Select vector map', - choices=self.layers, size=(120, 10)) + self.combo = wx.ComboBox(self.toolbar, id=4, value='Select vector map', + choices=self.layers, size=(-1, -1)) self.comboid = self.toolbar.AddControl(self.combo) + + self.toolbar.AddSeparator() + + self.point = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="point", + bitmap=wx.Bitmap(os.path.join(self.icons,"new.point.gif"), + wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, + kind=wx.ITEM_RADIO, + shortHelp="Digitize new point", + longHelp="") + self.line = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="line", + bitmap=wx.Bitmap(os.path.join(self.icons,"new.line.gif"), + wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, + kind=wx.ITEM_RADIO, + shortHelp="Digitize new line", + longHelp="") + + self.boundary = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="boundary", + bitmap=wx.Bitmap(os.path.join(self.icons,"new.boundary.gif"), + wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, + kind=wx.ITEM_RADIO, + shortHelp="Digitize new boundary", + longHelp="") + + self.centroid = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="centroid", + bitmap=wx.Bitmap(os.path.join(self.icons,"new.centroid.gif"), + wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, + kind=wx.ITEM_RADIO, + shortHelp="Digitize new centroid", + longHelp="") + self.toolbar.AddSeparator() - self.point = self.toolbar.AddLabelTool(wx.ID_ANY, "point", - wx.Bitmap(os.path.join(icons,"new.point.gif"), - wx.BITMAP_TYPE_ANY), - wx.NullBitmap, wx.ITEM_RADIO, "Digitize new point", "") - self.line = self.toolbar.AddLabelTool(wx.ID_ANY, "line", - wx.Bitmap(os.path.join(icons,"new.line.gif"), - wx.BITMAP_TYPE_ANY), - wx.NullBitmap, wx.ITEM_RADIO, "Digitize new line", - "") - self.boundary = self.toolbar.AddLabelTool(wx.ID_ANY, "boundary", - wx.Bitmap(os.path.join(icons,"new.boundary.gif"), - wx.BITMAP_TYPE_ANY), - wx.NullBitmap, wx.ITEM_RADIO, "Digitize new boundary", "") - self.centroid = self.toolbar.AddLabelTool(wx.ID_ANY, "centroid", - wx.Bitmap(os.path.join(icons,"new.centroid.gif"), - wx.BITMAP_TYPE_ANY), - wx.NullBitmap, wx.ITEM_RADIO, "Digitize new centroid", "") + self.exit = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="exit", + bitmap=wx.Bitmap(os.path.join(self.icons,"exit.gif"), + wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, + kind=wx.ITEM_NORMAL, + shortHelp="Quit digitization tool", + longHelp="") - self.parent.Bind(wx.EVT_TOOL, self.OnPoint, self.point) + # Bindings + self.parent.Bind(wx.EVT_TOOL, self.OnAddPoint, self.point) + self.parent.Bind(wx.EVT_TOOL, self.OnExit, self.exit) + self.parent.Bind(wx.EVT_COMBOBOX, self.OnSelectMap, self.comboid) + + def OnAddPoint(self,event): - def OnPoint(self,event): - - self.digitize="point" + self.action="addpoint" #self.parent.MapWindow.mouse['box'] = "point" - def AddPoint(self,x,y): - #east,north = self.parent.Pixel2Cell(x,y) - selectedmap=self.combo.GetCurrentSelection() - if selectedmap < 1: - print "you have to select map" - return + def OnExit (self, event): + """ + Quit digitization tool + """ + + self.parent.RemoveToolbar ("digit") - addstring="""P 1 - %f %f - """ % (x,y) - command = """v.edit -n map="%s" tool=add""" % (self.combo.GetValue()) - #print addstring, command - vedit=cmd.Command(command,stdin=addstring) - #try: - # kye,val = vedit.RunV() - # while 1: - # print key,val - # key,val = vedit.RunV() - #except: - # pass + def OnSelectMap (self, event): + """ + Select vector map to digitize + If any vector map is activated for digitization this action + is firstly terminated + """ + self.layerID = self.combo.GetCurrentSelection() + + # digitize (self.layers[self.layerID], mapset) + def _getListOfLayers(self): - layers =[""] + layers = [] + for layer in self.mapcontent.GetListOfLayers(l_type="vector"): - layers.append( layer.name) + layers.append (layer.name) + return layers - -#class pokus: -# def __init__(self): -# global icons -# print icons -# -#if __name__=="__main__": -# p = pokus() - - From landa at grass.itc.it Fri Apr 13 11:50:19 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Fri Apr 13 11:50:20 2007 Subject: [grass-addons] r487 - trunk/grassaddons/gui/gui_modules Message-ID: <200704130950.l3D9oJsB004598@grass.itc.it> Author: landa Date: 2007-04-13 11:50:19 +0200 (Fri, 13 Apr 2007) New Revision: 487 Modified: trunk/grassaddons/gui/gui_modules/cmd.py Log: bugfix (compat issue), usePopenClass added Modified: trunk/grassaddons/gui/gui_modules/cmd.py =================================================================== --- trunk/grassaddons/gui/gui_modules/cmd.py 2007-04-13 09:29:29 UTC (rev 486) +++ trunk/grassaddons/gui/gui_modules/cmd.py 2007-04-13 09:50:19 UTC (rev 487) @@ -16,11 +16,16 @@ for details. """ -import os +import os, sys + +usePopenClass = True + try: import subprocess except: - import combat.subprocess as subprocess + CombatPath = os.getenv("GISBASE") + "/etc/wx" + sys.path.append(CombatPath) + from compat import subprocess import grassenv @@ -64,26 +69,23 @@ os.environ["GRASS_MESSAGE_FORMAT"] = "gui" # run command - if 0: - (self.module_stdin, self.module_stdout, self.module_stderr) = \ - os.popen3(self.cmd) + if not usePopenClass: + (self.module_stdin, self.module_stdout, self.module_stderr) = \ + os.popen3(self.cmd) else: - self.module = subprocess.Popen(self.cmd, shell=True, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - close_fds=True) + self.module = subprocess.Popen(self.cmd, shell=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) + self.module_stdin = self.module.stdin + self.module_stderr = self.module.stderr - os.environ["GRASS_MESSAGE_FORMAT"] = "text" - - if self.module: - self.module_stdin = self.module.stdin - self.module_stderr = self.module.stderr - if stdin: - self.module_stdin.write(stdin) - self.module_stdin.close() + self.module_stdin.write(stdin) + self.module_stdin.close() + os.environ["GRASS_MESSAGE_FORMAT"] = "text" try: self.Run(verbose) @@ -146,7 +148,7 @@ # v.net.path silently, wait for process termination print "Running v.net.path for 0 593527.6875 4925297.0625 602083.875 4917545.8125..." - cmd = Command(cmd="v.net.path in=roads@PERMANENT out=tmp --o", + cmd = Command(cmd="v.net.path in=roads@PERMANENT out=tmp dmax=100000 --o", stdin="0 593527.6875 4925297.0625 602083.875 4917545.8125", verbose=False, wait=True) From landa at grass.itc.it Fri Apr 13 11:56:51 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Fri Apr 13 11:56:52 2007 Subject: [grass-addons] r488 - trunk/grassaddons/gui/gui_modules Message-ID: <200704130956.l3D9up4m004626@grass.itc.it> Author: landa Date: 2007-04-13 11:56:50 +0200 (Fri, 13 Apr 2007) New Revision: 488 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py Log: cosmetics: redundant " removed Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-13 09:50:19 UTC (rev 487) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-13 09:56:50 UTC (rev 488) @@ -1335,7 +1335,7 @@ # toolBar button handlers def onDecoration(self, event): """ - Decorations overlay menu" + Decorations overlay menu """ point = wx.GetMousePosition() decmenu = wx.Menu() @@ -1509,7 +1509,7 @@ def onZoomMenu(self, event): """ - Decorations overlay menu" + Decorations overlay menu """ point = wx.GetMousePosition() zoommenu = wx.Menu() From landa at grass.itc.it Fri Apr 13 12:11:23 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Fri Apr 13 12:11:24 2007 Subject: [grass-addons] r489 - trunk/grassaddons/gui/gui_modules Message-ID: <200704131011.l3DABNbt004673@grass.itc.it> Author: landa Date: 2007-04-13 12:11:23 +0200 (Fri, 13 Apr 2007) New Revision: 489 Modified: trunk/grassaddons/gui/gui_modules/toolbars.py Log: set size of comboboxes Modified: trunk/grassaddons/gui/gui_modules/toolbars.py =================================================================== --- trunk/grassaddons/gui/gui_modules/toolbars.py 2007-04-13 09:56:50 UTC (rev 488) +++ trunk/grassaddons/gui/gui_modules/toolbars.py 2007-04-13 10:11:23 UTC (rev 489) @@ -133,8 +133,8 @@ # # Optional toolbars # - self.combo = wx.ComboBox(self.toolbar, id=wx.ID_ANY, value='', - choices=['Digitize'], size=(-1, -1), style=wx.CB_READONLY , name='Tools') + self.combo = wx.ComboBox(parent=self.toolbar, id=wx.ID_ANY, value='Tools', + choices=['Digitize'], style=wx.CB_READONLY, size=(110, -1)) self.comboid = self.toolbar.AddControl(self.combo) @@ -187,8 +187,8 @@ self.initToolbar() def initToolbar(self): - self.combo = wx.ComboBox(self.toolbar, id=4, value='Select vector map', - choices=self.layers, size=(-1, -1)) + self.combo = wx.ComboBox(self.toolbar, id=wx.ID_ANY, value='Select vector map', + choices=self.layers, size=(150, -1)) self.comboid = self.toolbar.AddControl(self.combo) From landa at grass.itc.it Fri Apr 13 12:17:15 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Fri Apr 13 12:17:16 2007 Subject: [grass-addons] r490 - trunk/grassaddons/gui/gui_modules Message-ID: <200704131017.l3DAHFOe004693@grass.itc.it> Author: landa Date: 2007-04-13 12:17:15 +0200 (Fri, 13 Apr 2007) New Revision: 490 Modified: trunk/grassaddons/gui/gui_modules/digit.py Log: typo Modified: trunk/grassaddons/gui/gui_modules/digit.py =================================================================== --- trunk/grassaddons/gui/gui_modules/digit.py 2007-04-13 10:11:23 UTC (rev 489) +++ trunk/grassaddons/gui/gui_modules/digit.py 2007-04-13 10:17:15 UTC (rev 490) @@ -52,7 +52,7 @@ pass else: print "FAILURE" - for msg in vedit.msg: + for msg in vedit.module_msg: print msg[1] # From barton at grass.itc.it Fri Apr 13 22:05:43 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Fri Apr 13 22:05:44 2007 Subject: [grass-addons] r491 - trunk/grassaddons/gui/gui_modules Message-ID: <200704132005.l3DK5hTv013356@grass.itc.it> Author: barton Date: 2007-04-13 22:05:32 +0200 (Fri, 13 Apr 2007) New Revision: 491 Modified: trunk/grassaddons/gui/gui_modules/dbm.py trunk/grassaddons/gui/gui_modules/mapdisp.py trunk/grassaddons/gui/gui_modules/toolbars.py trunk/grassaddons/gui/gui_modules/wxgui_utils.py Log: Map displays made a child of layer tree. Makes for much more robust joint management of linked display and layer tree. Fixed a number of hidden bugs. Some refactoring. Modified: trunk/grassaddons/gui/gui_modules/dbm.py =================================================================== --- trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-13 10:17:15 UTC (rev 490) +++ trunk/grassaddons/gui/gui_modules/dbm.py 2007-04-13 20:05:32 UTC (rev 491) @@ -45,10 +45,10 @@ self.parent.SetStatusText(text_string.strip()) #---------------------------------------------------------------------- -# The panel you want to test (TestVirtualList) +# The panel you want to test (VirtualAttributeList) #---------------------------------------------------------------------- -class TestVirtualList(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.ColumnSorterMixin): +class VirtualAttributeList(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.ColumnSorterMixin): def __init__(self, parent,log,vectmap,pointdata=None): wx.ListCtrl.__init__( self, parent, -1, style=wx.LC_REPORT|wx.LC_HRULES|wx.LC_VRULES) #wx.VIRTUAL @@ -86,16 +86,12 @@ self.SetImageList(self.il, wx.IMAGE_LIST_SMALL) # show us the result in map display - if self.parent.gismanager: + if self.Parent.gismanager: - self.gism = self.parent.gismanager - curr_pg = self.gism.gm_cb.GetCurrentPage() - disp_idx = self.gism.track.Track().GetDisp_idx(curr_pg) + self.mapdisp = self.Parent.gismanager.curr_page.maptree.mapdisplay + self.map = self.Parent.gismanager.curr_page.maptree.Map - self.mapdisp = self.parent.gismanager.mapdisplays[disp_idx] - self.map = self.gism.maptree.Map - #building the columns i = 0 # FIXME: subprocess.Popen should be used @@ -151,7 +147,7 @@ #self.list.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick) #self.list.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown) - if self.parent.gismanager: + if self.Parent.gismanager: self.mapdisp.MapWindow.Bind(wx.EVT_LEFT_DOWN, self.onMapClick) self.timer = wx.PyTimer(self.RedrawMap) @@ -519,7 +515,7 @@ self.gismanager = parent # most importand part - self.win = TestVirtualList(self, log,vectmap=vectmap,pointdata=pointdata) + self.win = VirtualAttributeList(self, log,vectmap=vectmap,pointdata=pointdata) # buttons self.btn_apply = wx.Button(self, -1, "Apply") Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-13 10:17:15 UTC (rev 490) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-13 20:05:32 UTC (rev 491) @@ -53,7 +53,7 @@ else: icons = os.environ["GRASS_ICONPATH"] -Map = render.Map() # instance of Map class to render GRASS display output to PPM file +#Map = render.Map() # instance of Map class to render GRASS display output to PPM file # for cmdlinef cmdfilename = None @@ -69,7 +69,7 @@ global cmdfilename self.parent = parent - self.map = Map # + self.map = render.Map() # instance of Map class to render GRASS display output to PPM file self.cmdfile = open(cmdfilename,"r") def run(self): @@ -165,11 +165,13 @@ def __init__(self, parent, id, pos = wx.DefaultPosition, size = wx.DefaultSize, - style=wx.NO_FULL_REPAINT_ON_RESIZE): + style=wx.NO_FULL_REPAINT_ON_RESIZE, + Map=None, tree=None): wx.Window.__init__(self, parent, id, pos, size, style) self.parent = parent self.Map = Map + self.tree = tree # # Flags @@ -345,7 +347,7 @@ """ All that is needed here is to draw the buffer to screen """ - dc = wx.BufferedPaintDC(self, self._Buffer) + dc = wx.BufferedPaintDC(self) # use PrepateDC to set position correctly self.PrepareDC(dc) @@ -630,11 +632,11 @@ self.ovlcoords[self.dragid] = self.pdc.GetIdBounds(self.dragid) if self.dragid > 100: self.currtxtid = self.dragid - self.Parent.addText(None) + self.parent.addText(None) elif self.dragid == 0: - self.Parent.addBarscale(None) + self.parent.addBarscale(None) elif self.dragid == 1: - self.Parent.addLegend(None) + self.parent.addLegend(None) # left mouse button released and not just a pointer elif event.LeftUp(): @@ -663,10 +665,10 @@ # querying elif self.mouse["box"] == "query": east,north = self.Pixel2Cell(self.mouse['begin'][0],self.mouse['begin'][1]) - if self.parent.gismanager: - layer = self.parent.gismanager.maptree.GetSelection() - type = self.parent.gismanager.maptree.layertype[layer] - dcmd = self.parent.gismanager.maptree.GetPyData(layer)[0] + if self.tree.GetSelection(): + layer = self.tree.GetSelection() + type = self.tree.layertype[layer] + dcmd = self.tree.GetPyData(layer)[0] mapname = None for item in dcmd.split(' '): if 'map=' in item: @@ -817,11 +819,10 @@ or vector map. """ - if not self.parent.gismanager: return - if not self.parent.gismanager.maptree.GetSelection(): return - layer = self.parent.gismanager.maptree.GetSelection() - type = self.parent.gismanager.maptree.layertype[layer] - dcmd = self.parent.gismanager.maptree.GetPyData(layer)[0] + if not self.tree.GetSelection(): return + layer = self.tree.GetSelection() + type = self.tree.layertype[layer] + dcmd = self.tree.GetPyData(layer)[0] mapname = None for item in dcmd.split(' '): if 'map=' in item: @@ -948,7 +949,8 @@ def __init__(self, parent=None, id = wx.ID_ANY, title="Map display", pos=wx.DefaultPosition, size=wx.DefaultSize, - style=wx.DEFAULT_FRAME_STYLE, toolbars=["map"], cb=None, idx=-1): + style=wx.DEFAULT_FRAME_STYLE, toolbars=["map"], + cb=None, gismgr=None, Map=None): """ @@ -971,8 +973,9 @@ wx.Frame.__init__(self, parent, id, title, pos, size, style) # most of the thime, this will be the gis manager - self.gismanager = parent + self.gismanager = gismgr self.Map = Map + self.tree = parent # # Set the size @@ -1022,7 +1025,7 @@ # initialize buffered DC # self.MapWindow = DrawWindow(self) - self.MapWindow = BufferedWindow(self, id = wx.ID_ANY) # initialize buffered DC + self.MapWindow = BufferedWindow(self, id = wx.ID_ANY, Map=self.Map, tree=self.tree) # initialize buffered DC self.MapWindow.Bind(wx.EVT_MOTION, self.OnMotion) # @@ -1303,7 +1306,7 @@ """ # switch GIS Manager to output console to show query results - self.Parent.notebook.SetSelection(1) + self.gismanager.notebook.SetSelection(1) self.MapWindow.mouse['box'] = "query" self.MapWindow.zoomtype = 0 @@ -1629,7 +1632,7 @@ """ menuform.GUI().parseCommand(self.ovlcmd, gmpath, - completed=(self.Parent.getOptData,self.ovltype,self.params), + completed=(self.parent.getOptData,self.ovltype,self.params), parentframe=None) class TextDialog(wx.Dialog): Modified: trunk/grassaddons/gui/gui_modules/toolbars.py =================================================================== --- trunk/grassaddons/gui/gui_modules/toolbars.py 2007-04-13 10:17:15 UTC (rev 490) +++ trunk/grassaddons/gui/gui_modules/toolbars.py 2007-04-13 20:05:32 UTC (rev 491) @@ -8,15 +8,21 @@ import wx import os -import wxgui_utils +#import wxgui_utils import cmd -icons= os.path.split(wxgui_utils.icons)[0] -icons= os.path.split(icons)[0] -icons= os.path.split(icons)[0] +#icons= os.path.split(icons)[0] +#icons= os.path.split(icons)[0] +#icons= os.path.split(icons)[0] #print icons +if not os.getenv("GRASS_ICONPATH"): + icons = os.getenv("GISBASE") + "/etc/gui/icons/" +else: + icons = os.environ["GRASS_ICONPATH"] + + class MapToolbar: """ Main Map Display toolbar @@ -30,7 +36,7 @@ self.mapdisplay = mapdisplay self.toolbar = wx.ToolBar(parent=self.mapdisplay, id=wx.ID_ANY) - + #self.SetToolBar(self.toolbar) tsize = (24, 24) self.toolbar.SetToolBitmapSize(tsize) @@ -38,14 +44,14 @@ # # Draw # - self.displaymap = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="displaymap", - bitmap=wx.Bitmap(name=os.path.join(wxgui_utils.icons, "gui-display.gif")), - bmpDisabled=wx.NullBitmap, - kind=wx.ITEM_NORMAL, - shortHelp="Display map", - longHelp="") + + self.displaymap = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="displaymap", + bitmap=wx.Bitmap(name=os.path.join(icons,"gui-display.gif"), + type=wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, kind=wx.ITEM_NORMAL, + shortHelp="Display map", longHelp="") self.erase = self.toolbar.AddLabelTool(wx.ID_ANY, "erase", - wx.Bitmap(os.path.join(wxgui_utils.icons,"gui-erase.gif"), + wx.Bitmap(os.path.join(icons,"gui-erase.gif"), wx.BITMAP_TYPE_ANY), wx.NullBitmap, wx.ITEM_NORMAL, "Erase display", "") self.toolbar.AddSeparator() @@ -54,44 +60,44 @@ # Zooming, etc. # self.pointer = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="pointer", - bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"gui-pointer.gif"), + bitmap=wx.Bitmap(os.path.join(icons,"gui-pointer.gif"), wx.BITMAP_TYPE_ANY), bmpDisabled=wx.NullBitmap, kind=wx.ITEM_RADIO, shortHelp="Pointer", longHelp="") self.zoomin = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="zoom_in", - bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"gui-zoom_in.gif"), + bitmap=wx.Bitmap(os.path.join(icons,"gui-zoom_in.gif"), wx.BITMAP_TYPE_ANY), bmpDisabled=wx.NullBitmap, kind=wx.ITEM_RADIO, shortHelp="Zoom in", longHelp="Drag or click mouse to zoom") self.zoomout = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="zoom_out", - bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"gui-zoom_out.gif"), + bitmap=wx.Bitmap(os.path.join(icons,"gui-zoom_out.gif"), wx.BITMAP_TYPE_ANY), bmpDisabled=wx.NullBitmap, kind=wx.ITEM_RADIO, shortHelp="Zoom out", longHelp="Drag or click mouse to unzoom") self.pan = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="pan", - bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"gui-pan.gif"), + bitmap=wx.Bitmap(os.path.join(icons,"gui-pan.gif"), wx.BITMAP_TYPE_ANY), bmpDisabled=wx.NullBitmap, kind=wx.ITEM_RADIO, shortHelp="Pan", longHelp="Drag with mouse to pan") self.query = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="query", - bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"gui-query.gif"), - wx.BITMAP_TYPE_ANY), - bmpDisabled=wx.NullBitmap, - kind=wx.ITEM_RADIO, - shortHelp="Query", longHelp="Query selected map") + bitmap=wx.Bitmap(os.path.join(icons,"gui-query.gif"), + wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, + kind=wx.ITEM_RADIO, + shortHelp="Query", longHelp="Query selected map") self.toolbar.AddSeparator() self.zoomback = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="zoom_back", - bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"gui-zoom_back.gif"), + bitmap=wx.Bitmap(os.path.join(icons,"gui-zoom_back.gif"), wx.BITMAP_TYPE_ANY), bmpDisabled=wx.NullBitmap, kind=wx.ITEM_NORMAL, shortHelp="Zoom options", longHelp="Display zoom management") self.zoommenu = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="zoommenu", - bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"gui-mapzoom.gif"), + bitmap=wx.Bitmap(os.path.join(icons,"gui-mapzoom.gif"), wx.BITMAP_TYPE_ANY), bmpDisabled=wx.NullBitmap, shortHelp="Decoration", longHelp="Add graphic overlays to map") @@ -99,10 +105,10 @@ self.dec = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="dec", - bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"module-d.barscale.gif"), + bitmap=wx.Bitmap(os.path.join(icons,"module-d.barscale.gif"), wx.BITMAP_TYPE_ANY), bmpDisabled=wx.NullBitmap, - shortHelp="Decoration", + shortHelp="Decoration", longHelp="Add graphic overlays to map") self.toolbar.AddSeparator() @@ -111,7 +117,7 @@ # Misc # self.savefile = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="savefile", - #bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"file-save.gif"), + #bitmap=wx.Bitmap(os.path.join(icons,"file-save.gif"), #wx.BITMAP_TYPE_ANY), # just testing wx.ArtProvider bitmap=wx.ArtProvider.GetBitmap(id=wx.ART_FILE_SAVE, client=wx.ART_BUTTON, size=tsize), @@ -121,7 +127,7 @@ longHelp="") self.printmap = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="printmap", - #bitmap=wx.Bitmap(os.path.join(wxgui_utils.icons,"file-save.gif"), + #bitmap=wx.Bitmap(os.path.join(icons,"file-save.gif"), #wx.BITMAP_TYPE_ANY), # just testing wx.ArtProvider bitmap=wx.ArtProvider.GetBitmap(id=wx.ART_PRINT, client=wx.ART_BUTTON), @@ -183,7 +189,7 @@ # create toolbar self.toolbar = wx.ToolBar(parent=self.parent, id=wx.ID_ANY) self.toolbar.SetToolBitmapSize(wx.Size(24,24)) - + self.initToolbar() def initToolbar(self): @@ -191,9 +197,9 @@ choices=self.layers, size=(150, -1)) self.comboid = self.toolbar.AddControl(self.combo) - + self.toolbar.AddSeparator() - + self.point = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="point", bitmap=wx.Bitmap(os.path.join(self.icons,"new.point.gif"), wx.BITMAP_TYPE_ANY), @@ -240,7 +246,7 @@ self.parent.Bind(wx.EVT_TOOL, self.OnAddPoint, self.point) self.parent.Bind(wx.EVT_TOOL, self.OnExit, self.exit) self.parent.Bind(wx.EVT_COMBOBOX, self.OnSelectMap, self.comboid) - + def OnAddPoint(self,event): self.action="addpoint" @@ -250,7 +256,7 @@ """ Quit digitization tool """ - + self.parent.RemoveToolbar ("digit") def OnSelectMap (self, event): @@ -258,11 +264,11 @@ Select vector map to digitize If any vector map is activated for digitization this action - is firstly terminated + is firstly terminated """ self.layerID = self.combo.GetCurrentSelection() - + # digitize (self.layers[self.layerID], mapset) def _getListOfLayers(self): Modified: trunk/grassaddons/gui/gui_modules/wxgui_utils.py =================================================================== --- trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-13 10:17:15 UTC (rev 490) +++ trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-13 20:05:32 UTC (rev 491) @@ -7,7 +7,11 @@ import track import select import menuform +import mapdisp +import render + + #FIXME?? try: from subprocess import * @@ -35,7 +39,7 @@ size=wx.DefaultSize, style=wx.SUNKEN_BORDER, ctstyle=CT.TR_HAS_BUTTONS | CT.TR_HAS_VARIABLE_ROW_HEIGHT | CT.TR_HIDE_ROOT | CT.TR_ROW_LINES | CT.TR_FULL_ROW_HIGHLIGHT| - CT.TR_EDIT_LABELS, disp=None, log=None): + CT.TR_EDIT_LABELS, idx=None, gismgr=None, gm_cb=None): CT.CustomTreeCtrl.__init__(self, parent, id, pos, size, style,ctstyle) self.SetAutoLayout(True) @@ -43,7 +47,7 @@ self.EnableSelectionGradient(True) self.SetFirstGradientColour(wx.Colour(150, 150, 150)) - self.Map = "" # instance of render.Map associated with display + self.Map = render.Map() # instance of render.Map to be associated with display self.root = "" # ID of layer tree root node self.groupnode = 0 # index value for layers self.optpage = {} # dictionary of notebook option pages for each map layer @@ -53,9 +57,34 @@ self.saveitem = {} # dictionary to preserve layer attributes for drag and drop self.first = True # indicates if a layer is just added or not self.drag = False # flag to indicate a drag event is in process + self.disp_idx = idx + self.gismgr = gismgr + self.gm_cb = gm_cb # GIS Manager notebook for layer tree + self.treepg = parent # notebook page holding layer tree - self.Map = disp.getRender() + # init associated map display + self.mapdisplay = mapdisp.MapFrame(self, + id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, + style=wx.DEFAULT_FRAME_STYLE, + cb=self.gm_cb, gismgr=self.gismgr, Map=self.Map) + + # title + self.mapdisplay.SetTitle(_("Map Display " + str(self.disp_idx))) + #self.maptree[self.disp_idx] = self.mapdisplays[self.disp_idx].getTree() + + # store information about display and associated controls in a dictionary in track.py + track.Track().SetDisp(self.disp_idx,self.mapdisplay) + track.Track().SetCtrlDict(self.disp_idx, self.mapdisplay, self.treepg, self) + + #show new display + self.mapdisplay.Show() + self.mapdisplay.Refresh() + self.mapdisplay.Update() + + + self.Map = self.mapdisplay.getRender() + self.root = self.AddRoot("Map Layers") self.SetPyData(self.root, (None,None)) @@ -134,7 +163,7 @@ self.Bind(wx.EVT_TREE_BEGIN_DRAG, self.onBeginDrag) self.Bind(wx.EVT_TREE_END_DRAG, self.onEndDrag) - def AddLayer(self, idx, type): + def AddLayer(self, type): self.first = True params = {} # no initial options parameters @@ -507,6 +536,9 @@ self.Map.changeLayer(item=layer, command=cmd, l_active=chk, l_hidden=hidden, l_opacity=opac, l_render=False) + def setNotebookPage(self,pg): + self.Parent.notebook.SetSelection(pg) + class TreeCtrlComboPopup(wx.combo.ComboPopup): """ Create a tree ComboBox for selecting maps and other GIS elements @@ -729,10 +761,11 @@ # cmd = self.console_command.GetLineText(0) cmdlst = cmd.split(' ') try: - disp_idx = int(track.Track().GetDisp()[0]) - curr_disp = track.Track().GetDisp()[1] +# disp_idx = int(track.Track().GetDisp()[0]) +# curr_disp = track.Track().GetDisp()[1] + curr_disp = self.Parent.Parent.curr_page.maptree.mapdisplay except: - disp_idx = None +# disp_idx = None curr_disp = None if len(cmdlst) == 1 and cmd in gcmdlst: @@ -762,11 +795,11 @@ print 'Command type not yet implemented' return - if disp_idx != None: - # get layer tree for active display - layertree = track.Track().GetCtrls(disp_idx, 2) +# if disp_idx != None: +# # get layer tree for active display +# layertree = track.Track().GetCtrls(disp_idx, 2) # add layer - layertree.AddLayer(disp_idx, layertype) + self.Parent.Parent.curr_page.maptree.AddLayer(layertype) else: menuform.GUI().parseCommand(cmd, gmpath, parentframe=None) From barton at grass.itc.it Fri Apr 13 22:13:32 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Fri Apr 13 22:13:32 2007 Subject: [grass-addons] r492 - trunk/grassaddons/gui/gui_modules Message-ID: <200704132013.l3DKDWL9013802@grass.itc.it> Author: barton Date: 2007-04-13 22:13:25 +0200 (Fri, 13 Apr 2007) New Revision: 492 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py trunk/grassaddons/gui/gui_modules/wxgui_utils.py Log: small bug fix Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-13 20:05:32 UTC (rev 491) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-13 20:13:25 UTC (rev 492) @@ -950,7 +950,7 @@ def __init__(self, parent=None, id = wx.ID_ANY, title="Map display", pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE, toolbars=["map"], - cb=None, gismgr=None, Map=None): + cb=None, gismgr=None, idx=None, Map=None): """ @@ -1103,7 +1103,7 @@ self._mgr.DetachPane (self.digittoolbar.toolbar) self.digittoolbar.toolbar.Destroy() self.digittoolbar = None - + self.maptoolbar.combo.SetValue (""); self._mgr.Update() Modified: trunk/grassaddons/gui/gui_modules/wxgui_utils.py =================================================================== --- trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-13 20:05:32 UTC (rev 491) +++ trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-13 20:13:25 UTC (rev 492) @@ -66,7 +66,7 @@ self.mapdisplay = mapdisp.MapFrame(self, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE, - cb=self.gm_cb, gismgr=self.gismgr, Map=self.Map) + cb=self.gm_cb, gismgr=self.gismgr, idx=self.disp_idx, Map=self.Map) # title From barton at grass.itc.it Sat Apr 14 02:16:29 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Sat Apr 14 02:16:30 2007 Subject: [grass-addons] r493 - in trunk/grassaddons/gui: . gui_modules Message-ID: <200704140016.l3E0GTpA017753@grass.itc.it> Author: barton Date: 2007-04-14 02:16:18 +0200 (Sat, 14 Apr 2007) New Revision: 493 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py trunk/grassaddons/gui/gui_modules/wxgui_utils.py trunk/grassaddons/gui/wxgui.py Log: Further cleanup after making map displays children of layer tree. Probably don't need track.py anymore. Check for operation of cmd.py Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-13 20:13:25 UTC (rev 492) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-14 00:16:18 UTC (rev 493) @@ -950,7 +950,7 @@ def __init__(self, parent=None, id = wx.ID_ANY, title="Map display", pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE, toolbars=["map"], - cb=None, gismgr=None, idx=None, Map=None): + tree=None, notebook=None, gismgr=None, page=None, Map=None): """ @@ -966,16 +966,21 @@ style -- window style toolbars-- array of default toolbars, which should appear map, digit - cb -- control book ID in GIS Manager - idx -- index of display + notebook-- control book ID in GIS Manager + tree -- associated layer tree + gismgr -- GIS Manager panel + page -- notebook page with layer tree + Map -- instance of render.Map """ wx.Frame.__init__(self, parent, id, title, pos, size, style) # most of the thime, this will be the gis manager - self.gismanager = gismgr - self.Map = Map - self.tree = parent + self.gismanager = gismgr # GIS Manager object + self.Map = Map # instance of render.Map + self.tree = tree # GIS Manager layer tree object + self.page = page # Notebook page holding the layer tree + self.layerbook = notebook #GIS Manager layer tree notebook # # Set the size @@ -983,7 +988,6 @@ self.SetClientSize((600, 475)) # Set variables to associate display with GIS Manager page - self.ctrlbk = cb self.disp_idx = idx # @@ -1040,11 +1044,9 @@ # # Bind various events - # ONLY if we are running from GIS manager # - if self.disp_idx > -1: - self.Bind(wx.EVT_ACTIVATE, self.OnFocus) - self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) + self.Bind(wx.EVT_ACTIVATE, self.OnFocus) + self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) # # Update fancy gui style @@ -1118,27 +1120,13 @@ def OnFocus(self, event): """ - Store information about active display - in tracking variables and change choicebook + Change choicebook page to match display """ - #get index number of active display - self.disp_idx = int(track.Track().GetDisp_idx(self)) - #set active display tuple in track - track.Track().SetDisp(self.disp_idx, self) - - # change bookcontrol page to page associted with display if > 1 display - pg = track.Track().GetCtrls(self.disp_idx, 1) - pg_count = self.ctrlbk.GetPageCount() - pgnum = '0' - if pg_count > 0: - for x in range(0,pg_count): - if self.ctrlbk.GetPage(x) == pg: - pgnum = x - break - - self.ctrlbk.SetSelection(pgnum) + # change bookcontrol page to page associted with display + pgnum = self.layerbook.GetPageIndex(self.page) + if pgnum > -1: self.layerbook.SetSelection(pgnum) event.Skip() def OnMotion(self, event): @@ -1274,26 +1262,15 @@ def OnCloseWindow(self, event): """ Window closed + Also close associated layer tree page """ + pgnum = None self.Map.Clean() + pgnum = self.layerbook.GetPageIndex(self.page) self.Destroy() - #close associated controls book page - #get index number of active display - self.disp_idx = track.Track().GetDisp_idx(self) + if pgnum > -1: self.layerbook.DeletePage(pgnum) - # delete associated bookcontrol page if it exists - if self.disp_idx != None: - pg = track.Track().GetCtrls(self.disp_idx, 1) - pg_count = self.ctrlbk.GetPageCount() - pgnum = '0' - if pg_count > 0: - for x in range(0,pg_count): - if self.ctrlbk.GetPage(x) == pg: - pgnum = x - track.Track().popCtrl(self.disp_idx) - self.ctrlbk.DeletePage(pgnum) - def getRender(self): """ returns the current instance of render.Map() Modified: trunk/grassaddons/gui/gui_modules/wxgui_utils.py =================================================================== --- trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-13 20:13:25 UTC (rev 492) +++ trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-14 00:16:18 UTC (rev 493) @@ -39,7 +39,7 @@ size=wx.DefaultSize, style=wx.SUNKEN_BORDER, ctstyle=CT.TR_HAS_BUTTONS | CT.TR_HAS_VARIABLE_ROW_HEIGHT | CT.TR_HIDE_ROOT | CT.TR_ROW_LINES | CT.TR_FULL_ROW_HIGHLIGHT| - CT.TR_EDIT_LABELS, idx=None, gismgr=None, gm_cb=None): + CT.TR_EDIT_LABELS, idx=None, gismgr=None, notebook=None): CT.CustomTreeCtrl.__init__(self, parent, id, pos, size, style,ctstyle) self.SetAutoLayout(True) @@ -59,14 +59,16 @@ self.drag = False # flag to indicate a drag event is in process self.disp_idx = idx self.gismgr = gismgr - self.gm_cb = gm_cb # GIS Manager notebook for layer tree + self.notebook = notebook # GIS Manager notebook for layer tree self.treepg = parent # notebook page holding layer tree + # init associated map display self.mapdisplay = mapdisp.MapFrame(self, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE, - cb=self.gm_cb, gismgr=self.gismgr, idx=self.disp_idx, Map=self.Map) + tree=self, notebook=self.notebook, gismgr=self.gismgr, page=self.treepg, + Map=self.Map) # title @@ -83,6 +85,7 @@ self.mapdisplay.Update() + self.Map = self.mapdisplay.getRender() self.root = self.AddRoot("Map Layers") Modified: trunk/grassaddons/gui/wxgui.py =================================================================== --- trunk/grassaddons/gui/wxgui.py 2007-04-13 20:13:25 UTC (rev 492) +++ trunk/grassaddons/gui/wxgui.py 2007-04-14 00:16:18 UTC (rev 493) @@ -137,6 +137,8 @@ self.disp_idx = 0 #index value for map displays and layer trees self.maptree = {} #dictionary to index a layer tree to accompanying a map display self.mapfocus = 0 #track which display currently has focus + self.curr_page = '' # currently selected page for layer tree notebook + self.curr_pagenum = '' # currently selected page number for layer tree notebook self.Bind(wx.EVT_CLOSE, self.onCloseWindow) self.Bind(wx.EVT_LEFT_DOWN, self.addRaster) @@ -246,32 +248,28 @@ old_pgnum = event.GetOldSelection() new_pgnum = event.GetSelection() - curr_pg = self.gm_cb.GetCurrentPage() - sel_pgnum = self.gm_cb.GetSelection() + self.curr_page = self.gm_cb.GetCurrentPage() + self.curr_pagenum = self.gm_cb.GetSelection() + try: + self.curr_page.maptree.mapdisplay.SetFocus() + self.curr_page.maptree.mapdisplay.Raise() + except: + pass - # get ID of associated display if more than one - disp_idx = track.Track().GetDisp_idx(curr_pg) - if disp_idx != None: - #get associated display and make it active - newdisp = track.Track().GetCtrls(disp_idx, 0) - newdisp.SetFocus() - newdisp.Raise() event.Skip() def onCBPageClosed(self, event): - """Page of notebook closed""" + """ + Page of notebook closed + Also close associated map display + """ + closepage = event.GetSelection() - curr_pg = self.gm_cb.GetCurrentPage() - disp_idx = track.Track().GetDisp_idx(curr_pg) - if disp_idx != None: - #get associated display and make it active - disp = track.Track().GetCtrls(disp_idx, 0) - try: - if self.mapdisplays.has_key(disp_idx): - if self.mapdisplays[disp_idx].Close(False): - self.mapdisplays[disp_idx].Close(True) - except: - pass + try: + if self.closepage.maptree.mapdisplay.Close(False): + self.closepage.maptree.mapdisplay.Close(True) + except: + pass def runCmd(self,event): """Run command""" @@ -323,13 +321,15 @@ ) def ShowAttributeTable(self,event): - - maptype = self.maptree.layertype[self.maptree.GetSelection()] + if self.curr_page.maptree.GetSelection() not in self.curr_page.maptree.layertype: return + maptype = self.curr_page.maptree.layertype[self.curr_page.maptree.GetSelection()] if maptype != 'vector': print 'Attribute management only available for vector files' return - dcmd = self.maptree.GetPyData(self.maptree.GetSelection())[0] + if not self.curr_page.maptree.GetPyData(self.curr_page.maptree.GetSelection()): return + dcmd = self.curr_page.maptree.GetPyData(self.curr_page.maptree.GetSelection())[0] + if not dcmd: return mapname = map = mapset = size = icon = None for item in dcmd.split(' '): if 'map=' in item: @@ -351,46 +351,26 @@ def newDisplay(self, event=None): """Create new map display frame""" - newdisp = self.mapdisplays[self.disp_idx] = mapdisp.MapFrame(self, - id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, - style=wx.DEFAULT_FRAME_STYLE, - cb=self.gm_cb, idx=self.disp_idx) - # title - newdisp.SetTitle(_("Map Display " + str(self.disp_idx))) - #self.maptree[self.disp_idx] = self.mapdisplays[self.disp_idx].getTree() - - #add notebook page to GIS Manager - # make a new page in the bookcontrol for the layer tree (on page 0 of the notebook) self.pg_panel = wx.Panel(self.gm_cb, id=wx.ID_ANY, style= wx.EXPAND) self.gm_cb.AddPage(self.pg_panel, text="Display "+ str(self.disp_idx), select = True) - self.cb_page = self.gm_cb.GetCurrentPage() + self.curr_page = self.gm_cb.GetCurrentPage() # create layer tree (tree control for managing GIS layers) and put on new notebook page - self.maptree = wxgui_utils.LayerTree(self.cb_page, id=wx.ID_ANY, pos=wx.DefaultPosition, + self.curr_page.maptree = wxgui_utils.LayerTree(self.curr_page, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.TR_HAS_BUTTONS |wx.TR_LINES_AT_ROOT|wx.TR_EDIT_LABELS|wx.TR_HIDE_ROOT |wx.TR_DEFAULT_STYLE|wx.NO_BORDER|wx.FULL_REPAINT_ON_RESIZE, - disp=newdisp) + idx=self.disp_idx, gismgr=self, notebook=self.gm_cb) # layout for controls cb_boxsizer = wx.BoxSizer(wx.VERTICAL) - cb_boxsizer.Add(self.maptree, proportion=1, flag=wx.EXPAND, border=1) - self.cb_page.SetSizer(cb_boxsizer) - cb_boxsizer.Fit(self.cb_page) - self.cb_page.Layout() - #self.cb_page.SetAutoLayout(True) - #self.Centre() + cb_boxsizer.Add(self.curr_page.maptree, proportion=1, flag=wx.EXPAND, border=1) + self.curr_page.SetSizer(cb_boxsizer) + cb_boxsizer.Fit(self.curr_page.maptree) + self.curr_page.Layout() + self.curr_page.maptree.Layout() - # store information about display and associated controls in a dictionary in track.py - track.Track().SetDisp(self.disp_idx,self) - track.Track().SetCtrlDict(self.disp_idx, newdisp, self.cb_page, self.maptree) - - #show new display - self.mapdisplays[self.disp_idx].Show() - self.mapdisplays[self.disp_idx].Refresh() - self.mapdisplays[self.disp_idx].Update() - self.disp_idx += 1 # toolBar button handlers @@ -489,75 +469,62 @@ def addRaster(self, event): - self.SetTree('raster') + self.curr_page.maptree.AddLayer('raster') def addRGB(self, event): """Add RGB layer""" - self.SetTree('rgb') + self.curr_page.maptree.AddLayer('rgb') def addHIS(self, event): """Add HIS layer""" - self.SetTree('his') + self.curr_page.maptree.AddLayer('his') def addRastLeg(self, event): """Add raster legend""" - self.SetTree('rastleg') + self.curr_page.maptree.AddLayer('rastleg') def addVector(self, event): """Add vector layer""" - self.SetTree('vector') + self.curr_page.maptree.AddLayer('vector') def addThemeMap(self, event): """Add thematic map layer""" - self.SetTree('thememap') + self.curr_page.maptree.AddLayer('thememap') def addThemeChart(self, event): """Add thematic chart layer""" - self.SetTree('themechart') + self.curr_page.maptree.AddLayer('themechart') def addCommand(self, event): """Add command line layer""" - self.SetTree('command') + self.curr_page.maptree.AddLayer('command') def addGroup(self, event): """Add layer group""" - self.SetTree('group') + self.curr_page.maptree.AddLayer('group') def addGrid(self, event): """Add layer grid""" - self.SetTree('grid') + self.curr_page.maptree.AddLayer('grid') def addLabels(self, event): """Add layer vector labels""" - print 'labels 1', event - self.SetTree('labels') + self.curr_page.maptree.AddLayer('labels') def GetSelectedDisplay(self): return self.notebook.GetSelection() - def SetTree(self, layertype): - """ - Add map display layer in GIS Manager tree widget - """ - disp_idx = track.Track().GetDisp_idx(self.maptree) - if disp_idx != None: - self.maptree.AddLayer(disp_idx, layertype) - def deleteLayer(self, event): """ Delete selected map display layer in GIS Manager tree widget """ - self.maptree.Delete(self.maptree.GetSelection()) + self.curr_page.maptree.Delete(self.curr_page.maptree.GetSelection()) #Misc methods def onCloseWindow(self, event): '''Cleanup when wxgui.py is quit''' - mdlist = range(0, self.disp_idx+1) try: - for md in mdlist: - if self.mapdisplays.has_key(md): - if self.mapdisplays[md].Close(False): - self.mapdisplays[md].Close(True) + self.DeleteAllPages() except: self.DestroyChildren() self.Destroy() From barton at grass.itc.it Sat Apr 14 03:36:19 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Sat Apr 14 03:36:20 2007 Subject: [grass-addons] r494 - trunk/grassaddons/gui Message-ID: <200704140136.l3E1aJ4w018305@grass.itc.it> Author: barton Date: 2007-04-14 03:36:11 +0200 (Sat, 14 Apr 2007) New Revision: 494 Modified: trunk/grassaddons/gui/wxgui.py Log: Adding a layer to the layer tree automatically switches to layer tree view. Modified: trunk/grassaddons/gui/wxgui.py =================================================================== --- trunk/grassaddons/gui/wxgui.py 2007-04-14 00:16:18 UTC (rev 493) +++ trunk/grassaddons/gui/wxgui.py 2007-04-14 01:36:11 UTC (rev 494) @@ -469,46 +469,57 @@ def addRaster(self, event): + self.notebook.SetSelection(0) self.curr_page.maptree.AddLayer('raster') def addRGB(self, event): """Add RGB layer""" + self.notebook.SetSelection(0) self.curr_page.maptree.AddLayer('rgb') def addHIS(self, event): """Add HIS layer""" + self.notebook.SetSelection(0) self.curr_page.maptree.AddLayer('his') def addRastLeg(self, event): """Add raster legend""" + self.notebook.SetSelection(0) self.curr_page.maptree.AddLayer('rastleg') def addVector(self, event): """Add vector layer""" + self.notebook.SetSelection(0) self.curr_page.maptree.AddLayer('vector') def addThemeMap(self, event): """Add thematic map layer""" + self.notebook.SetSelection(0) self.curr_page.maptree.AddLayer('thememap') def addThemeChart(self, event): """Add thematic chart layer""" + self.notebook.SetSelection(0) self.curr_page.maptree.AddLayer('themechart') def addCommand(self, event): """Add command line layer""" + self.notebook.SetSelection(0) self.curr_page.maptree.AddLayer('command') def addGroup(self, event): """Add layer group""" + self.notebook.SetSelection(0) self.curr_page.maptree.AddLayer('group') def addGrid(self, event): """Add layer grid""" + self.notebook.SetSelection(0) self.curr_page.maptree.AddLayer('grid') def addLabels(self, event): """Add layer vector labels""" + self.notebook.SetSelection(0) self.curr_page.maptree.AddLayer('labels') def GetSelectedDisplay(self): From barton at grass.itc.it Sat Apr 14 07:09:28 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Sat Apr 14 07:09:30 2007 Subject: [grass-addons] r495 - trunk/grassaddons/gui/gui_modules Message-ID: <200704140509.l3E59SW2000814@grass.itc.it> Author: barton Date: 2007-04-14 07:09:19 +0200 (Sat, 14 Apr 2007) New Revision: 495 Modified: trunk/grassaddons/gui/gui_modules/menuform.py Log: Error trapping improved. Modified: trunk/grassaddons/gui/gui_modules/menuform.py =================================================================== --- trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-14 01:36:11 UTC (rev 494) +++ trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-14 05:09:19 UTC (rev 495) @@ -757,7 +757,7 @@ if type( porf[ 'wxId' ] ) == type( 1 ) and porf['wxId'] == myId: porf[ 'value' ] = me.GetValue() self.updateStatusLine() - + def createCmd(self, ignoreErrors = False): """Produce a command line string for feeding into GRASS. @@ -834,7 +834,7 @@ else: get_dcmd = completed[0] layer = completed[1] - dcmd_params.update(completed[2]) + if completed[2]: dcmd_params.update(completed[2]) cmdlst = cmd.split(' ') if parentframe != -1: From barton at grass.itc.it Sat Apr 14 07:10:24 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Sat Apr 14 07:10:26 2007 Subject: [grass-addons] r496 - trunk/grassaddons/gui Message-ID: <200704140510.l3E5AOHD000835@grass.itc.it> Author: barton Date: 2007-04-14 07:10:16 +0200 (Sat, 14 Apr 2007) New Revision: 496 Modified: trunk/grassaddons/gui/wxgui.py Log: Select multiple layers to delete Modified: trunk/grassaddons/gui/wxgui.py =================================================================== --- trunk/grassaddons/gui/wxgui.py 2007-04-14 05:09:19 UTC (rev 495) +++ trunk/grassaddons/gui/wxgui.py 2007-04-14 05:10:16 UTC (rev 496) @@ -529,7 +529,10 @@ """ Delete selected map display layer in GIS Manager tree widget """ - self.curr_page.maptree.Delete(self.curr_page.maptree.GetSelection()) + for layer in self.curr_page.maptree.GetSelections(): + if self.curr_page.maptree.layertype[layer] == 'group': + self.curr_page.maptree.DeleteChildren(layer) + self.curr_page.maptree.Delete(layer) #Misc methods def onCloseWindow(self, event): From barton at grass.itc.it Sat Apr 14 07:27:34 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Sat Apr 14 07:27:36 2007 Subject: [grass-addons] r497 - trunk/grassaddons/gui/gui_modules Message-ID: <200704140527.l3E5RYFw001150@grass.itc.it> Author: barton Date: 2007-04-14 07:27:26 +0200 (Sat, 14 Apr 2007) New Revision: 497 Modified: trunk/grassaddons/gui/gui_modules/wxgui_utils.py Log: Improved management of selected/unselected items. Modified: trunk/grassaddons/gui/gui_modules/wxgui_utils.py =================================================================== --- trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-14 05:10:16 UTC (rev 496) +++ trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-14 05:27:26 UTC (rev 497) @@ -39,7 +39,7 @@ size=wx.DefaultSize, style=wx.SUNKEN_BORDER, ctstyle=CT.TR_HAS_BUTTONS | CT.TR_HAS_VARIABLE_ROW_HEIGHT | CT.TR_HIDE_ROOT | CT.TR_ROW_LINES | CT.TR_FULL_ROW_HIGHLIGHT| - CT.TR_EDIT_LABELS, idx=None, gismgr=None, notebook=None): + CT.TR_EDIT_LABELS|CT.TR_MULTIPLE, idx=None, gismgr=None, notebook=None): CT.CustomTreeCtrl.__init__(self, parent, id, pos, size, style,ctstyle) self.SetAutoLayout(True) @@ -170,6 +170,8 @@ self.first = True params = {} # no initial options parameters + if self.layer_selected: self.SelectItem(self.layer_selected, select=False) + if type == 'command': # generic command layer self.ctrl = wx.TextCtrl(self, id=wx.ID_ANY, value='', @@ -314,6 +316,8 @@ self.Map.delLayer(item=layer) self.layertype.pop(layer) + self.Unselect() + self.layer_selected = None def onLayerChecked(self, event): layer = event.GetItem() @@ -372,10 +376,12 @@ def onBeginDrag(self, event): """ Drag and drop of single tree nodes """ - self.drag = True + # node cannot be a parent if self.GetChildrenCount(event.GetItem()) == 0: event.Allow() + self.drag = True + self.DoSelectItem(event.GetItem(), unselect_others=True) # save everthing associated with item to drag self.dragItem = event.GetItem() From neteler at grass.itc.it Sat Apr 14 07:35:34 2007 From: neteler at grass.itc.it (neteler@grass.itc.it) Date: Sat Apr 14 07:35:38 2007 Subject: [grass-addons] r498 - in trunk/grassaddons: . grassflyer grassflyer/flyer1 grassflyer/flyer1/pix Message-ID: <200704140535.l3E5ZY8x001209@grass.itc.it> Author: neteler Date: 2007-04-14 07:35:33 +0200 (Sat, 14 Apr 2007) New Revision: 498 Added: trunk/grassaddons/grassflyer/ trunk/grassaddons/grassflyer/README trunk/grassaddons/grassflyer/flyer1/ trunk/grassaddons/grassflyer/flyer1/Makefile trunk/grassaddons/grassflyer/flyer1/grassflyer.tex trunk/grassaddons/grassflyer/flyer1/leaflet.cls trunk/grassaddons/grassflyer/flyer1/pix/ trunk/grassaddons/grassflyer/flyer1/pix/OSGeo_CMYK.pdf trunk/grassaddons/grassflyer/flyer1/pix/grasslogo_vector.pdf trunk/grassaddons/grassflyer/flyer1/pix/isodist.png trunk/grassaddons/grassflyer/flyer1/pix/ndvi.png trunk/grassaddons/grassflyer/flyer1/pix/trento3d.pdf trunk/grassaddons/grassflyer/flyer1/pix/visibility.png Log: GRASS Flyer 1 Added: trunk/grassaddons/grassflyer/README =================================================================== --- trunk/grassaddons/grassflyer/README (rev 0) +++ trunk/grassaddons/grassflyer/README 2007-04-14 05:35:33 UTC (rev 498) @@ -0,0 +1,6 @@ +GRASS Flyer + +Contact: +Malte Halbey-Martin (GRASS Promotion Manager) + + Added: trunk/grassaddons/grassflyer/flyer1/Makefile =================================================================== --- trunk/grassaddons/grassflyer/flyer1/Makefile (rev 0) +++ trunk/grassaddons/grassflyer/flyer1/Makefile 2007-04-14 05:35:33 UTC (rev 498) @@ -0,0 +1,19 @@ +vectoreps := $(wildcard *.eps) +vectorfinal := $(patsubst %.eps,%.pdf,$(vectoreps)) + +default: grassflyer.pdf + +$(vectorfinal): $(vectoreps) + eps2eps -sOutputFile=- $< | epstopdf -f + +%.pdf: %.tex + pdflatex $< + pdflatex $< + +clean: + rm -f *.aux *.log *.out + +distclean: clean + rm -f *.pdf + +.PHONY: clean distclean default Added: trunk/grassaddons/grassflyer/flyer1/grassflyer.tex =================================================================== --- trunk/grassaddons/grassflyer/flyer1/grassflyer.tex (rev 0) +++ trunk/grassaddons/grassflyer/flyer1/grassflyer.tex 2007-04-14 05:35:33 UTC (rev 498) @@ -0,0 +1,198 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%GRASS PROMOTION FLYER % +%(c) 2007 GRASS PROMOTION TEAM % +%GNU Free Documentation License % +%Version 1.2 % +%Needs leaflet.cls % +%www.ctan.org/tex-archive/macros/latex/contrib/leaflet/% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%Sometimes printing engines need the 2nd side upside down +%in this case, use tumble (which is default) instead of notumble +%If this causes problems, use notumble +%If you need a foldmark, delete nofoldmark +\documentclass[notumble,a4paper,10pt,nofoldmark]{leaflet} +\usepackage{helvet,courier,xcolor} + +% Set Helvetica as the default font +\renewcommand*\familydefault\sfdefault +% Let LaTeX knows that pictures are found in ./pix +\graphicspath{{pix/}} + +% Setting up things for the captions +\usepackage{caption}[2004/07/16] +\captionsetup{% + font={small,it},% + labelformat=empty,% Leaves out label: ``Figure 1'' + labelsep=none,% + aboveskip=0pt% +} +% Defining a new 'figure' environment for the document +\newenvironment{myfig}[1][0pt plus 1.5ex minus .5ex]{\par\vspace*{#1}\begin{minipage}{\textwidth}\centering}{\end{minipage}} + +% Defining the GRASS homepage +\newcommand{\GRASSurl}{\url{http://grass.itc.it}} + +% Define a color for the URIs +\definecolor{darkblue}{RGB}{0,0,88} + +\usepackage{hyperref} +% Setting up some document info +\hypersetup{% + colorlinks=true,% + urlcolor=darkblue,% Redefine this color to change URIs color + pdfauthor={The GRASS Community},% + pdftitle={GRASS GIS: Efficiency through Freedom \& Transparency},% + pdfsubject={GRASS Promotion Flyer},% + breaklinks=true,% + plainpages=false% +} + +% Title page stuff +\title{\textbf{\huge GRASS GIS}\\% +\textsl{Efficiency through Freedom \& Transparency}} +\author{The GRASS Community} +\date{\includegraphics[width=\textwidth]{grasslogo_vector}\\[2ex] +\large\GRASSurl} + +\begin{document} + +\maketitle +\thispagestyle{empty}% Necessary to leave out the page number on the first page + +\newpage + +\section{What is GRASS} + +GRASS (Geographic Resources Analysis Support System) is a free and Open Source Software for performing spatial analysis. It consists of more than 350 modules for processing vector (2D/3D), raster and voxel data. Many interfaces to other programs in related domains like geostatistics, databases, mapserver and even other GIS software exist. It is the largest Open Source GIS. It can serve as a Desktop GIS and as the backbone of a complete GIS Infrastructure. + +\section{Where is GRASS used} + +GRASS is used in scientific applications, commercial settings and by public authorities all over the world. GRASS has shown strong potential for solving geospatial problems in numerous situations world-wide. + +\section{History} + +GRASS was originally developed in the beginning of the 1980's by the US Army Construction Engineering Research Laboratories (USA-CERL) and was published as public domain software. When the USA-CERL withdrew from GRASS development, an international developer team took over this work. Since 1999, GRASS has been published as free software under the terms of the GNU General Public Licence. +\begin{myfig}[1.5ex] +\includegraphics[width=0.7\textwidth]{visibility} +\captionof{figure}{Viewshed analysis performed with GRASS} +\end{myfig} + +\section{Open Source Philosophy} + +The Open Source philosophy provides the user the ability to see the source code and structure of the program which offers a great transparency. Users can extend the program for their own needs. Immediate souce code peer review increases the quality. With the help of the extension manager new modules can be created without GRASS package source code. + +\section{Technical Data Sheet} + +\subsection{License} + +GNU General Public License (Free Software Foundation) + +\subsection{Supported platforms} + +GRASS runs on nearly all platforms. It supports GNU/Linux, Posix compliant Unix Systems, MS-Windows and MacOS X. + +\subsection{Design} + +\begin{itemize} +\item Modular +\item Consists of more than 350 modules +\end{itemize} + +\subsection{Programming Languages} + +\begin{itemize} +\item ANSI C +\item GRASS- SWIG interface +\item Python for WebGIS applications +\item Java Version: JGRASS +\end{itemize} + +\subsection{Data Management Capabilities} + +\begin{itemize} +\item Raster / Vector / Voxel data processing +\item 2D / 3D Raster / Vector modelling +\item Image manipulation +\item Vector topology / Network analysis +\item Geostatistics (Interface to R) +\end{itemize} + +\begin{myfig}[1ex] +\includegraphics[width=0.7\textwidth]{trento3d} +\captionof{figure}{A flyby of the city of Trento, Italy} +\end{myfig} + +\section{Supported File Formats} + +GRASS supports nearly all common GIS file formats through the use of the GDAL/OGR library. In addition it supports the Open GIS Consortium's Simple Features. + +\subsection{Vector File formats} +ASCII, ARC/INFO ungenerate, ARC/INFO E00, Arc\-View SHAPE, BIL, DLG (U.S.), DXF, DXF3D, GMT, GPS-ASCII USGS-DEM, IDRISI, MOSS, MapInfo MIF, TIGER, VRML, \dots + +\subsection{Raster File Formats} +ASCII, ARC/GRID, E00, GIF, GMT, TIF, PNG, Vis5D, SURFER (.grd),\dots +\begin{myfig} +\includegraphics[width=0.7\textwidth]{isodist} +\captionof{figure}{Default GUI configuration showing GRASS network analysis capabilites} +\end{myfig} + +\subsection{Image File Formats} + +CEOS (SAR, SRTM, LANDSAT7 etc.), ERDAS LAN / IMG, HDF, LANDSAT TM/MSS, NHAP aerial photos, SAR, SPOT, \dots +\begin{myfig}[1.5ex] +\includegraphics[width=0.7\textwidth]{ndvi} +\captionof{figure}{Image processing capabilities of GRASS} +\end{myfig} + +\subsection{Database support} + +\begin{itemize} +\item PostgreSQL / PostGIS +\item MySQL +\item SQLite +\item ODBC +\item DBF +\end{itemize} + +\subsection{Output} + +\begin{itemize} +\item Modules for creating maps +\item NVIZ for visualization of 2.5D and 3D data (creation of animations \& flybys) +%\item{GMT export} +%item{VRML} +\item VTK, POVray +\item WebGIS via Mapserver, Python, etc. +\end{itemize} + +\subsection{Interoperability to other GIS- related Software} + +\begin{itemize} +\item Quantum GIS (Free Geodata Viewer and more) +\item R- Language (Statistics) +\item Gstat (Geostatistics) +\item UMN Mapserver (Webmapping) +\end{itemize} + +\section{Where to find more information} + +\begin{itemize} +%\begin{flushleft} +\item{Project Website: \\\GRASSurl} +\item{GRASS Wiki: \\\url{http://grass.gdf.hannover.de/wiki}} +\item{GRASS Promotion Team: \\\url{malte@perlomat.de}} +\item{GRASS mailing lists: \\\url{http://grass.itc.it/community/support.php}} +%\end{flushleft} +\end{itemize} + +\vfill +\section{OSGeo} + +GRASS is a founding project of the Open Source Geospatial Foundation which has the aim to create high quality open source geospatial software. For further information visit the OSGeo homepage: +\begin{center} +\includegraphics[width=0.8\textwidth]{OSGeo_CMYK}\\ +\url{http://www.osgeo.org} +\end{center} + +\end{document} Added: trunk/grassaddons/grassflyer/flyer1/leaflet.cls =================================================================== --- trunk/grassaddons/grassflyer/flyer1/leaflet.cls (rev 0) +++ trunk/grassaddons/grassflyer/flyer1/leaflet.cls 2007-04-14 05:35:33 UTC (rev 498) @@ -0,0 +1,503 @@ +%% +%% This is file `leaflet.cls', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% leaflet.dtx (with options: `class') +%% +%% Copyright (C) 2003, 2004 +%% Rolf Niepraschk, Rolf.Niepraschk@ptb.de +%% Hubert Gaesslein, HubertJG@open.mind.de +%% +%% This work may be distributed and/or modified under the +%% conditions of the LaTeX Project Public License, either version 1.3 +%% of this license or (at your option) any later version. +%% The latest version of this license is in +%% http://www.latex-project.org/lppl.txt +%% and version 1.3 or later is part of all distributions of LaTeX +%% version 2003/12/01 or later. +%% +%% This work has the LPPL maintenance status "author-maintained". +%% +\NeedsTeXFormat{LaTeX2e}[1999/12/01] +\ProvidesClass{leaflet} + [2004/12/22 v1.0d LaTeX document class (JS,WaS,RN,HjG)] +\let\LL@shipout\shipout \let\LL@outputpage\@outputpage +\let\LL@begindvi\@begindvi \let\LL@@end\@@end +\@ifundefined{iflandscape}{\newif\iflandscape}{}% +\@ifundefined{iftumble}{\newif\iftumble}{}% +\newcommand\LL@debug@info[1]{}% +\DeclareOption{dvips}{\PassOptionsToPackage{\CurrentOption}{graphics}} +\DeclareOption{pdftex}{\PassOptionsToPackage{\CurrentOption}{graphics}} +\DeclareOption{vtex}{\PassOptionsToPackage{\CurrentOption}{graphics}} +\DeclareOption{dvipdfm}{\PassOptionsToPackage{\CurrentOption}{graphics}} +\DeclareOption{twoside}{\OptionNotUsed} +\DeclareOption{twocolumn}{\OptionNotUsed} +\DeclareOption{landscape}{\landscapetrue} +\DeclareOption{portrait}{\landscapefalse} +\DeclareOption{debug}{\let\LL@debug@info\typeout} +\DeclareOption{nospecialtricks}{% + \AtEndOfClass{% + \ifLL@combine + \let\immediate\@@@immediate\let\write\@@@write + \let\openout\@@@openout\let\closeout\@@@closeout + \let\special\@@@special\let\@@@exec@outs\relax + \fi}} +\newcommand*\LL@setPaperSize{} +\DeclareOption{a3paper}{\def\LL@setPaperSize{% + \paperwidth=420mm\paperheight=297mm\relax}}% +\@ifdefinable\ifLL@combine{\newif\ifLL@combine} +\DeclareOption{combine}{\LL@combinetrue} +\DeclareOption{nocombine}{\LL@combinefalse} +\newcommand*\LL@selectOutput{} +\DeclareOption{frontside}{\def\LL@selectOutput#1#2{#1}} +\DeclareOption{backside}{\def\LL@selectOutput#1#2{#2}} +\DeclareOption{bothsides}{\def\LL@selectOutput#1#2{#1#2}} +\DeclareOption{tumble}{\tumbletrue} +\DeclareOption{notumble}{\tumblefalse} +\newcommand*\LL@foldmark{} +\DeclareOption{foldmark}{% + \def\LL@foldmark{% + \begingroup + \linethickness{\LenToUnit{\foldmarkrule}}% + \setlength\@tempdima{\paperheight-\LL@tmargin}% + \put(0,\LenToUnit{\@tempdima}){% + \line(0,-1){\LenToUnit{\foldmarklength}}}% + \endgroup}% +} +\DeclareOption{nofoldmark}{\def\LL@foldmark{}}% +\newcommand*\LL@toomanypages[2]{} +\DeclareOption{draft}{\PassOptionsToClass{\CurrentOption}{article}% + \AtEndOfClass{% + \def\LL@toomanypages#1#2{% + \ClassWarningNoLine{leaflet}{#1.\MessageBreak#2}}% + }% +} +\DeclareOption{final}{\PassOptionsToClass{\CurrentOption}{article}% + \AtEndOfClass{% + \ifLL@combine + \def\LL@toomanypages#1#2{% + \ClassError{leaflet}{#1}{#2.}}% + \else + \def\LL@toomanypages#1#2{% + \ClassWarningNoLine{leaflet}{#1.\MessageBreak#2}}% + \fi + }% +} +\DeclareOption*{\PassOptionsToClass{\CurrentOption}{article}} +\PassOptionsToClass{landscape,a4paper}{article} +\ExecuteOptions{tumble,foldmark,bothsides,combine,landscape,final} +\ProcessOptions\relax +\ifLL@combine + \newcommand*\LL@rotate@I{}\newcommand*\LL@rotate@II{}% + \iflandscape + \def\LL@rotate@I#1{#1}% + \iftumble + \def\LL@rotate@II#1{\rotatebox[origin=c]{180}{#1}}% + \else + \def\LL@rotate@II#1{#1}% + \fi + \else + \def\LL@rotate@I#1{\rotatebox[origin=c]{90}{#1}}% + \iftumble + \def\LL@rotate@II#1{\rotatebox[origin=c]{270}{#1}}% + \else + \def\LL@rotate@II#1{\rotatebox[origin=c]{90}{#1}}% + \fi + \fi + \def\@@@pending@outs{}\let\@@@immediate\immediate + \let\@@@write\write \let\@@@special\special + \let\@@@openout\openout \let\@@@closeout\closeout + \def\immediate{% + \let\write\immediate@write% + \let\openout\immediate@openout% + \let\closeout\immediate@closeout% + \let\special\immediate@special}% + \def\reset@immediate{% + \let\write\pending@write% + \let\openout\pending@openout% + \let\closeout\pending@closeout% + \let\special\@@@special}% + \long\def\pending@write#1#{\pending@@write{#1}} + \def\immediate@write{% + \reset@immediate\@@@immediate\@@@write}% + \def\immediate@openout{% + \reset@immediate\@@@immediate\@@@openout}% + \def\immediate@closeout{% + \reset@immediate\@@@immediate\@@@closeout}% + \def\immediate@special{% + \reset@immediate\@@@immediate\@@@special}% + \let\write\pending@write + \let\openout\pending@openout + \let\closeout\pending@closeout + \def\@dummy@whatsit{\special{}} + \begingroup\@ifundefined{pdfoutput}% + {\endgroup} + {\endgroup + \ifnum\pdfoutput>\z@\def\@dummy@whatsit{\pdfliteral{}}\fi} + \begingroup\expandafter\expandafter\expandafter\endgroup + \expandafter\ifx\csname eTeXversion\endcsname\relax + %%% Test is from Markus Kohm (d.c.t.t, 29 Jun 2004) + \ClassWarningNoLine{leaflet}{% + *************************************\MessageBreak + * It's very recommended to use eTeX \MessageBreak + * with this package! \MessageBreak + *************************************}% + \long\def\pending@@write#1#2{% + \@dummy@whatsit + \g@addto@macro\@@@pending@outs{\@@@immediate\@@@write\number#1{#2},}}% + \def\pending@openout#1 {% + \@dummy@whatsit + \g@addto@macro\@@@pending@outs{\@@@immediate\@@@openout\number#1,}}% + \def\pending@closeout#1{% + \@dummy@whatsit + \g@addto@macro\@@@pending@outs{\@@@immediate\@@@closeout\number#1,}}% + \newcommand*\@@@exec@outs{% + \@@@pending@outs\gdef\@@@pending@outs{}% + \LL@debug@info{% + >>> execute the output commands of the current page <<<}}% + \else + \RequirePackage{etex} + \globmarks\@@@out@mark + \newcounter{@@total@outs}\setcounter{@@total@outs}{0} + \newcounter{@@last@exec}\setcounter{@@last@exec}{0} + \long\def\pending@@write#1#2{% + \global\advance\c@@@total@outs\@ne% + \marks\@@@out@mark{\the\c@@@total@outs}% + \g@addto@macro\@@@pending@outs{\@@@immediate\@@@write\number#1{#2},}}% +\def\pending@openout#1 {% + \global\advance\c@@@total@outs\@ne% + \marks\@@@out@mark{\the\c@@@total@outs}% + \g@addto@macro\@@@pending@outs{\@@@immediate\@@@openout\number#1,}}% +\def\pending@closeout#1{% + \global\advance\c@@@total@outs\@ne% + \marks\@@@out@mark{\the\c@@@total@outs}% + \g@addto@macro\@@@pending@outs{\@@@immediate\@@@closeout\number#1,}}% + \newcommand*\@@@exec@outs{% + \begingroup + \@tempcntb\c@@@total@outs\advance\@tempcntb-\c@@@last@exec% + \edef\reserved@a{\botmarks\@@@out@mark}% + \ifx\reserved@a\@empty\@tempcnta\z@\else\@tempcnta\reserved@a\fi% + \LL@debug@info{PENDING-OUTS:\the\@tempcntb\space\space + TOTAL-OUTS:\the\c@@@total@outs\space\space + LAST-EXEC:\the\c@@@last@exec\space\space + TOPMARK:\topmarks\@@@out@mark\space\space + FIRSTMARK:\firstmarks\@@@out@mark\space\space + BOTMARK:\botmarks\@@@out@mark}% + \advance\@tempcnta-\c@@@total@outs \advance\@tempcntb\@tempcnta + \@tempcnta-\@tempcnta% + \ifnum\@tempcnta>\z@ + \LL@debug@info{% + >>> resave \the\@tempcnta\space output command(s). + Too early to execute! <<<}% + \fi + \@tempcnta\z@ \def\reserved@b{}% + \@for\reserved@a :=\@@@pending@outs\do{% + \ifx\reserved@a\@empty\else + \ifnum\@tempcnta<\@tempcntb% + \reserved@a% execute output's related to the current page box. + \global\advance\c@@@last@exec\@ne + \LL@debug@info{>>> execute output command number + \the\c@@@last@exec\space<<<}% + \else + \expandafter\g@addto@macro\expandafter\reserved@b\expandafter{% + \reserved@a,}% + \fi + \advance\@tempcnta\@ne% + \fi}% + \expandafter\@temptokena\expandafter{\reserved@b}% + \xdef\@@@pending@outs{\the\@temptokena}% + \endgroup}% + \fi% end of eTeX test. + \long\def\protected@write#1#2#3{% + \begingroup + \let\thepage\relax + #2% + \let\protect\@unexpandable@protect + \edef\reserved@a{\noexpand\write#1{#3}}% + \reserved@a% + \endgroup + \if@nobreak\ifvmode\nobreak\fi\fi}% + \def\shipout{\deadcycles\z@\setbox\@tempboxa=} + \let\@begindvi\@empty +\fi% end of \ifLL@combine +\LoadClass{article} +\RequirePackage{everyshi,calc,graphicx} +\newcommand*\LL@pagesize@specials[2]{} +\@ifundefined{Gin@driver}{}% +{% + \ifx\Gin@driver\@empty\else% + \filename@parse{\Gin@driver}\@tempswafalse% + \def\reserved@a{dvips}% + \ifx\filename@base\reserved@a\@tempswatrue\fi% + \def\reserved@a{dvipdfm}% + \ifx\filename@base\reserved@a\@tempswatrue\fi% + \if@tempswa% + \ClassInfo{leaflet}{Generating code for dvips}% + \def\LL@pagesize@specials#1#2{% + \@tempdima=#1\@tempdimb=#2% + \AtBeginDvi{\special{papersize=\the\@tempdima,\the\@tempdimb}}}% + \fi% + \def\reserved@a{pdftex}% + \ifx\filename@base\reserved@a + \ClassInfo{leaflet}{Generating code for pdfTeX}% + \def\LL@pagesize@specials#1#2{% + \@tempdima=#1\@tempdimb=#2% + \pdfpagewidth\@tempdima\pdfpageheight\@tempdimb}% + \fi% + \def\reserved@a{vtex}% + \ifx\filename@base\reserved@a + \ClassInfo{leaflet}{Generating code for VTeX}% + \def\LL@pagesize@specials#1#2{% + \@tempdima=#1\@tempdimb=#2% + \mediawidth\@tempdima\mediaheight\@tempdimb}% + \fi% + \fi +} +\newcommand*\LL@CmdIgnored[1]{% + \ClassWarning{leaflet}{% + `\string#1' ignored}} +\setlength{\parskip}{1ex plus 2pt} +\@listi% +\setlength{\labelwidth}{\leftmargin} +\addtolength{\labelwidth}{-\labelsep} +\pagestyle{empty} +\headheight\z@ +\headsep\z@ +\footskip\z@ +\marginparwidth\z@ +\marginparsep\z@ +\sloppy +\setcounter{secnumdepth}{0} +\renewcommand\twocolumn[1][]{\LL@CmdIgnored{\twocolumn}} +\renewcommand\onecolumn{\LL@CmdIgnored{\onecolumn}} +\renewcommand\topfraction{0.7} +\renewcommand\bottomfraction{0.7} +\setlength{\textfloatsep}{10pt plus 4pt minus 3pt} +\setlength{\parindent}{\z@} +\setlength{\leftmargini}{1.5em} +\setlength{\leftmarginii}{1.5em} +\setlength{\leftmarginiii}{1.5em} +\setlength{\leftmarginiv}{1.5em} +\setlength{\leftmarginv}{1.5em} +\setlength{\leftmarginvi}{1.5em} +\setlength{\labelsep}{.5em} +\setlength \labelwidth{\leftmargini} +\addtolength\labelwidth{-\labelsep} +\def\noparskip{\par\vspace{-\parskip}} +\let\old@small\small +\renewcommand{\small}{\old@small\let\@listi\@listI} +\let\old@footnotesize\footnotesize +\renewcommand{\footnotesize}{\old@footnotesize\let\@listi\@listI} +\newcommand{\sectfont}{\bfseries} +\renewcommand\section{\@startsection{section}{1}{\z@}% + {-3.5ex \@plus -.75ex}% + {1ex} %{1.5ex}% + {\normalfont\large\sectfont}} +\renewcommand\subsection{\@startsection{subsection}{2}{\z@}% + {-2.5ex plus -.5ex}% + {1\p@} %{1ex}% + {\normalfont\normalsize\sectfont}} +\renewcommand\subsubsection{\@startsection{subsubsection}{3}{\z@}% + {-2.5ex plus -.5ex}% + {-1em}% + {\normalfont\normalsize\sectfont}} +\def\part{\LL@CmdIgnored{\part}\secdef\@part\@spart} +\def\@part[#1]#2{} +\def\@spart#1{} + +\renewcommand*\descriptionlabel[1]{% + \hspace\labelsep\normalfont\descfont #1} +\newcommand*\descfont{\bfseries} +\iffalse +\g@addto@macro\enumerate{\parsep2\p@\@plus2\p@\@minus\z@} +\g@addto@macro\itemize{\parsep2\p@\@plus2\p@\@minus\z@} +\g@addto@macro\description{\parsep2\p@\@plus2\p@\@minus\z@} +\else +\newcommand*\LL@listsetup{% + \parsep1ex\@plus.5ex\@minus.25ex% + \LL@debug@info{***parsep=\the\parsep}% + \itemsep\z@ + \LL@debug@info{***itemsep=\the\itemsep}% + \topsep\z@ + \LL@debug@info{***topsep=\the\topsep}% + \LL@debug@info{***partopsep=\the\partopsep}% +} +\def\enumerate{% + \ifnum \@enumdepth >\thr@@\@toodeep\else + \advance\@enumdepth\@ne + \edef\@enumctr{enum\romannumeral\the\@enumdepth}% + \expandafter + \list + \csname label\@enumctr\endcsname + {\usecounter\@enumctr + \def\makelabel##1{\hss\llap{##1}}% + %\def\makelabel##1{##1\hfill}% + %\def\makelabel##1{\hss##1}% + \LL@listsetup + }% + \fi} +\def\itemize{% + \ifnum \@itemdepth >\thr@@\@toodeep\else + \advance\@itemdepth\@ne + \edef\@itemitem{labelitem\romannumeral\the\@itemdepth}% + \expandafter + \list + \csname\@itemitem\endcsname + {% + \def\makelabel##1{\hss\llap{##1}}% + %\def\makelabel##1{##1\hfill}% + %\def\makelabel##1{\hss##1}% + \LL@listsetup + }% + \fi} +\renewenvironment{description} + {\list{}{\labelwidth\z@ \itemindent-\leftmargin + \let\makelabel\descriptionlabel + \LL@listsetup}} + {\endlist} +\fi +\newcommand*\setmargins[4]{% + \setlength\topmargin{#1}% + \edef\LL@tmargin{\the\topmargin}% + \setlength\evensidemargin{#2}% + \setlength\textheight{% + \paperheight-\topmargin-\evensidemargin% + -\headheight-\headsep-\footskip}% + \setlength\oddsidemargin{#3}% + \setlength\evensidemargin{#4}% + \setlength\textwidth{% + \paperwidth-\oddsidemargin-\evensidemargin-\marginparwidth-\marginparsep}% + \addtolength\topmargin{-1in}% + \addtolength\oddsidemargin{-1in}% + \evensidemargin\oddsidemargin% +} +\LL@setPaperSize +\paperwidth=0.333333334\paperwidth +\setmargins{11mm}{11mm}{8mm}{8mm} +\newcommand*\foldmarkrule{0.4pt} +\newcommand*\foldmarklength{2mm} +\newcommand\AddToBackground{% + \@ifstar{\@tempswatrue\LL@AddToBackground} + {\@tempswafalse\LL@AddToBackground}} +\@onlypreamble\AddToBackground +\newcommand\LL@AddToBackground[2]{% + \if@tempswa\def\@tempa{LL@largePic}\else\def\@tempa{LL@smallPic}\fi + \expandafter\providecommand\csname\@tempa\@Roman{#1}\endcsname{}% + \expandafter\g@addto@macro\csname\@tempa\@Roman{#1}\endcsname{#2}} +\newcommand\LenToUnit[1]{#1\@gobble} +\newcommand*\CutLine{% + \@ifstar{\@tempswatrue\LL@CutLine}{\@tempswafalse\LL@CutLine}} +\@onlypreamble\CutLine +\newcommand*\LL@CutLine[1]{% + \ifLL@combine + \ifx\Scissors\@empty\@tempswatrue\fi + \if@tempswa + \AddToBackground{#1}{% + \put(0,0){% + \rotatebox{90}{\makebox(\LenToUnit{\paperheight},0){% + \normalsize + \dotfill}}}}% + \else + \AddToBackground{#1}{% + \put(0,0){% + \rotatebox{90}{\makebox(\LenToUnit{\paperheight},0){% + \normalsize + \dotfill\Scissors\dotfill\dotfill\Scissors\dotfill}}}}% + \fi + \fi} +\IfFileExists{pifont.sty} + {\RequirePackage{pifont}% + \newcommand*\Scissors{\raisebox{-0.85ex}{\large\ding{34}}}}% + {\newcommand*\Scissors{}} +\AddToBackground{3}{\LL@foldmark} +\providecommand*\vb@xt@{\vbox to} +\AtBeginDocument{\EveryShipout{\LL@savePage}} +\newcounter{LL@page}\setcounter{LL@page}{1} +\newcommand\LL@tempa{} +\newcommand*\LL@savePage{% + \ifnum\c@LL@page<7\relax + \setbox\@cclv\vbox{% + \vbox{\@tempdima=1in\relax + \@tempdimb=\paperheight\advance\@tempdimb-\@tempdima + \pictur@(0,0)(\LenToUnit{\@tempdima},\LenToUnit{\@tempdimb})% + \begingroup + \set@typeset@protect + \@nameuse{LL@smallPic\Roman{LL@page}}% + %\set@display@protect + \endgroup + \endpicture}% + \nointerlineskip\box\@cclv}% + \ifLL@combine + \@@@exec@outs + \expandafter\newsavebox\csname LL@box\Roman{LL@page}\endcsname% + \setbox\@cclv=\vbox{\vskip1in\unvbox\@cclv}% + \setbox\@cclv=\vbox{\moveright1in\box\@cclv}% + \setbox\@cclv=\hb@xt@\paperwidth{\box\@cclv\hss}% + \setbox\@cclv=\vb@xt@\paperheight{\box\@cclv\vss}% + \global\expandafter\setbox% + \csname LL@box\Roman{LL@page}\endcsname=\box\@cclv% + \typeout{\@spaces[\the\c@LL@page] ==> [\Roman{LL@page}]}% + \fi + \fi + \ifnum\c@LL@page=7\relax + \begingroup + \set@typeset@protect + \LL@toomanypages{% + The text you supplied fills more than six pages\MessageBreak + and will therefore not fit onto a single flyer}{% + Try using smaller fonts or reducing vertical space}% + \endgroup + \fi + \stepcounter{LL@page}} +\ifLL@combine + \def\@@end{% + \clearpage\pagestyle{empty}% + \let\@outputpage\LL@outputpage + \def\@EveryShipout@Hook{}% + \def\@EveryShipout@AtNextHook{}% + \EveryShipout{\LL@savePage}% + \loop\ifnum\c@LL@page<7\relax + \ClassInfo{leaflet}{Generating empty page \the\c@page}% + \null\newpage + \repeat + \let\shipout\LL@shipout \let\@begindvi\LL@begindvi + \paperwidth=3\paperwidth + \iflandscape + \LL@pagesize@specials{\paperwidth}{\paperheight}% + \else + \LL@pagesize@specials{\paperheight}{\paperwidth}% + \fi + \newcommand*\LL@shipoutPage[1]{% + \let \protect \noexpand + \shipout\vb@xt@\paperheight{% + \set@typeset@protect + \vskip-1in% + \@begindvi\hb@xt@\paperwidth{\hskip-1in##1\hss}\vss}}% + \newcommand*\LL@preparePages[3]{% + \typeout{[\@Roman{##1}\space\@Roman{##2}\space\@Roman{##3}] ==>}% + \pictur@(0,0)\@nameuse{LL@largePic\Roman{page}}\endpicture% + \LL@preparePage{##1}\LL@preparePage{##2}\LL@preparePage{##3}}% + \newcommand*\LL@preparePage[1]{% + \expandafter\box\csname LL@box\@Roman{##1}\endcsname}% + \LL@selectOutput + {\setcounter{page}{1}% + \LL@shipoutPage{\LL@rotate@I{\LL@preparePages{5}{6}{1}}}}% + {\setcounter{page}{2}% + \LL@shipoutPage{\LL@rotate@II{\LL@preparePages{2}{3}{4}}}}% + \LL@@end + }% +\else + \LL@pagesize@specials{\paperwidth}{\paperheight}% + \AtEndDocument{% + \clearpage\pagestyle{empty}% + \loop\ifnum\c@LL@page<7\relax + \ClassInfo{leaflet}{Generating empty page \the\c@page}% + \null\newpage + \repeat + } +\fi +\endinput +%% +%% End of file `leaflet.cls'. Added: trunk/grassaddons/grassflyer/flyer1/pix/OSGeo_CMYK.pdf =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/grassflyer/flyer1/pix/OSGeo_CMYK.pdf ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/grassflyer/flyer1/pix/grasslogo_vector.pdf =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/grassflyer/flyer1/pix/grasslogo_vector.pdf ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/grassflyer/flyer1/pix/isodist.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/grassflyer/flyer1/pix/isodist.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/grassflyer/flyer1/pix/ndvi.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/grassflyer/flyer1/pix/ndvi.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/grassflyer/flyer1/pix/trento3d.pdf =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/grassflyer/flyer1/pix/trento3d.pdf ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/grassflyer/flyer1/pix/visibility.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/grassflyer/flyer1/pix/visibility.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream From calvelo at grass.itc.it Sat Apr 14 08:02:03 2007 From: calvelo at grass.itc.it (calvelo@grass.itc.it) Date: Sat Apr 14 08:02:05 2007 Subject: [grass-addons] r499 - trunk/grassaddons/gui/gui_modules Message-ID: <200704140602.l3E623Vv002399@grass.itc.it> Author: calvelo Date: 2007-04-14 08:01:34 +0200 (Sat, 14 Apr 2007) New Revision: 499 Modified: trunk/grassaddons/gui/gui_modules/menuform.py Log: Added self-testing in standalone: call with 'test' as command. Still far from complete. Started refactoring so that menuform becomes usable from something like a qgisgrass-style toolbox: - added hidden fields, for preset values - factored out xml parsing from task description, so as to feed arbitrary task descritions - added error handling for missing fields in the params and flags structure: only name and description are now compulsory Modified: trunk/grassaddons/gui/gui_modules/menuform.py =================================================================== --- trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-14 05:35:33 UTC (rev 498) +++ trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-14 06:01:34 UTC (rev 499) @@ -36,6 +36,7 @@ import wx import sys +import re import string import textwrap import select @@ -55,17 +56,15 @@ gettext.install("wxgrass") sys.path.append(os.path.join(os.getenv("GISBASE"),"etc","wx")) +imagepath = os.path.join(os.getenv("GISBASE"),"etc","wx","images") +sys.path.append(imagepath) + try: import subprocess except: from compat import subprocess -import re -imagepath = os.getenv("GISBASE") + "/etc/wx/images/" -#imagepath = os.sep.join( os.getcwd().split(os.sep) [:-1] + ['images'] ) -sys.path.append(imagepath) - def reexec_with_pythonw(): if sys.platform == 'darwin' and\ not sys.executable.endswith('MacOS/Python'): @@ -110,9 +109,12 @@ for c in range(0,len(t_rgb)): str2rgb[ t_color[c] ] = t_rgb[ c ] rgb2str[ t_rgb[ c ] ] = t_color[ c ] +del t_colors +del t_color +del t_rgb def color_resolve(color): - if color[0] in "0123456789": + if len(color)>0 and color[0] in "0123456789": rgb = tuple(map(int,color.split( ':' ))) label = color else: @@ -122,7 +124,7 @@ label = color except KeyError: rgb = (200,200,200) - label = 'Select Color' + label = _('Select Color') return (rgb, label) @@ -159,7 +161,7 @@ """This class holds the structures needed for both filling by the parser and use by the interface constructor.""" def __init__(self): - self.name = 'unknown' + self.name = _('unknown') self.params = [] self.description = '' self.flags = [] @@ -323,9 +325,8 @@ contents.append( l ) self.SetPage( "".join( contents ) ) self.Ok = True - except: + except: # The Manual file was not found self.Ok = False - raise class mainFrame(wx.Frame): @@ -354,12 +355,12 @@ self.SetIcon(wx.Icon(os.path.join(imagepath,'grass.form.gif'), wx.BITMAP_TYPE_ANY)) menu = wx.Menu() - menu.Append(wx.ID_ABOUT, "&About GrassGUI", - "Information about GrassGUI") - menu.Append(ID_ABOUT_COMMAND, "&About " + self.task.name, - "Short descripton of GRASS command " + self.task.name) + menu.Append(wx.ID_ABOUT, _("&About GrassGUI"), + _("Information about GrassGUI") ) + menu.Append(ID_ABOUT_COMMAND, _("&About %s") % self.task.name, + _("Short descripton of GRASS command %s") % self.task.name) menu.AppendSeparator() - menu.Append(wx.ID_EXIT, "E&xit", "Terminate the program") + menu.Append(wx.ID_EXIT, _("E&xit"), _("Terminate the program") ) menuBar = wx.MenuBar() menuBar.Append(menu, "&File"); @@ -466,7 +467,7 @@ wx.TheClipboard.UsePrimarySelection(True) wx.TheClipboard.SetData(cmddata) wx.TheClipboard.Close() - self.SetStatusText("'%s' copied to clipboard" %\ + self.SetStatusText( _("'%s' copied to clipboard") %\ (self.createCmd(ignoreErrors=True))) def OnCancel(self, event): @@ -476,10 +477,10 @@ self.Destroy() def OnAbout(self, event): - dlg = wx.MessageDialog(self, "This is a sample program for\n" + dlg = wx.MessageDialog(self, _("This is a sample program for\n" "GRASS command interface parsing\n" - "and automatic GUI building. \n%s" %(__version__), - "About GrassGUI", wx.OK | wx.ICON_INFORMATION) + "and automatic GUI building. \n%s") %(__version__), + _("About GrassGUI"), wx.OK | wx.ICON_INFORMATION) dlg.ShowModal() dlg.Destroy() @@ -503,19 +504,19 @@ self.task = task # Determine tab layout - sections = ['Main'] + sections = [ _('Main') ] is_section = {} for task in self.task.params + self.task.flags: if not task.has_key('guisection') or task['guisection']=='': task['guisection'] = 'Options' if not is_section.has_key(task['guisection']): is_section[task['guisection']] = 1 - if task['guisection'] != 'Main': # check for pre-existing parameters passed from layer tree + if task['guisection'] != _('Main'): # check for pre-existing parameters passed from layer tree sections.append( task['guisection'] ) there_is_main = False for i in self.task.params+self.task.flags: if i.has_key('required') and i['required'] == 'yes': - i['guisection'] = 'Main' + i['guisection'] = _('Main') there_is_main = True if not there_is_main: sections = sections[1:] @@ -548,23 +549,23 @@ notebook.SetSelection(0) panelsizer.Add( notebook, 1, flag=wx.EXPAND ) - - for p in self.task.params: + visible_params = [ p for p in self.task.params if not p.get( 'hidden', 'no' ) == 'yes' ] + for p in visible_params: which_sizer = tabsizer[ p['guisection'] ] which_panel = tab[ p['guisection'] ] - title = text_beautify(p['description']) + title = text_beautify( p['description'] ) text_style = wx.FONTWEIGHT_BOLD txt = None - if p['required'] == 'no': + if p.get('required','no') == 'no': text_style = wx.FONTWEIGHT_NORMAL - if p['multiple'] == 'yes' and len( p['values'] ) == 0: + if p.get('multiple','no') == 'yes' and len( p.get('values','') ) == 0: title = _("[multiple]") + " " + title - if p[ 'value'] == '' : - p['value'] = p['default'] - if (len(p['values']) > 0): + if p.get('value','') == '' : + p['value'] = p.get('default','') + if ( len(p.get('values',[]) ) > 0): - valuelist=map(str,p['values']) - if p['multiple'] == 'yes': + valuelist=map(str,p.get('values',[])) + if p.get('multiple','no') == 'yes': txt = wx.StaticBox(which_panel,0,title+":") hSizer=wx.StaticBoxSizer( txt, wx.VERTICAL ) isDefault = {} @@ -581,12 +582,12 @@ chkbox.Bind(wx.EVT_CHECKBOX, self.OnCheckBoxMulti) which_sizer.Add( hSizer, 0, wx.ADJUST_MINSIZE, 5) elif len(valuelist) == 1: - txt = wx.StaticText(which_panel, label = title + - '. Valid range=' + str(valuelist).strip("[]'") + ':' ) + txt = wx.StaticText(which_panel, + label = _('%s. Valid range=%s') % (title, str(valuelist).strip("[]'") + ':' ) ) which_sizer.Add(txt, 0, wx.ADJUST_MINSIZE | wx.ALL, 5) - txt2 = wx.TextCtrl(which_panel, value = p['default'], + txt2 = wx.TextCtrl(which_panel, value = p.get('default',''), size = (STRING_ENTRY_WIDTH, ENTRY_HEIGHT)) - if p['value'] != '': txt2.SetValue(p['value']) # parameter previously set + if p.get('value','') != '': txt2.SetValue(p['value']) # parameter previously set which_sizer.Add( txt2, 0, wx.ADJUST_MINSIZE, 5) p['wxId'] = txt2.GetId() @@ -594,48 +595,50 @@ else: txt = wx.StaticText(which_panel, label = title + ':' ) which_sizer.Add(txt, 0, wx.ADJUST_MINSIZE | wx.ALL, 5) - cb = wx.ComboBox(which_panel, -1, p['default'], + cb = wx.ComboBox(which_panel, -1, p.get('default',''), wx.Point(-1, -1), wx.Size(STRING_ENTRY_WIDTH, -1), valuelist, wx.CB_DROPDOWN) - if p['value'] != '': cb.SetValue(p['value']) # parameter previously set + if p.get('value','') != '': cb.SetValue(p['value']) # parameter previously set which_sizer.Add( cb, 0, wx.ADJUST_MINSIZE, 5) p['wxId'] = cb.GetId() cb.Bind( wx.EVT_COMBOBOX, self.OnSetValue) # text entry - if (p['type'] in ('string','integer','float') - and len(p['values']) == 0 - and p['gisprompt'] == False - and p['prompt'] != 'color'): + if (p.get('type','string') in ('string','integer','float') + and len(p.get('values',[])) == 0 + and p.get('gisprompt',False) == False + and p.get('prompt','') != 'color'): txt = wx.StaticText(which_panel, label = title + ':' ) which_sizer.Add(txt, 0, wx.ADJUST_MINSIZE | wx.ALL, 5) - txt3 = wx.TextCtrl(which_panel, value = p['default'], + txt3 = wx.TextCtrl(which_panel, value = p.get('default',''), size = (STRING_ENTRY_WIDTH, ENTRY_HEIGHT)) - if p['value'] != '': txt3.SetValue(p['value']) # parameter previously set + if p.get('value','') != '': txt3.SetValue(p['value']) # parameter previously set which_sizer.Add( txt3, 0, wx.ADJUST_MINSIZE| wx.ALL, 5) p['wxId'] = txt3.GetId() txt3.Bind(wx.EVT_TEXT, self.OnSetValue) - if p['type'] == 'string' and p['gisprompt'] == True: + if p.get('type','string') == 'string' and p.get('gisprompt',False) == True: txt = wx.StaticText(which_panel, label = title + ':') which_sizer.Add(txt, 0, wx.ADJUST_MINSIZE | wx.ALL, 5) # element selection tree combobox (maps, icons, regions, etc.) - if p['prompt'] != 'color': + if p.get('prompt','') != 'color': selection = select.Select(which_panel, id=wx.ID_ANY, size=(300,-1), - type=p['element']) - if p['value'] != '': selection.SetValue(p['value']) # parameter previously set + type=p.get('element','') ) + if p.get('value','') != '': selection.SetValue(p['value']) # parameter previously set which_sizer.Add( selection, 0, wx.ADJUST_MINSIZE| wx.ALL, 5) # A select.Select is a combobox with two children: a textctl and a popupwindow; # we target the textctl here p['wxId'] = selection.GetChildren()[0].GetId() selection.Bind(wx.EVT_TEXT, self.OnSetValue) # color entry - elif p['prompt'] == 'color': - if p['default'] != '': + elif p.get('prompt','') == 'color': + default_color = (200,200,200) + label_color = _("Select Color") + if p.get('default','') != '': default_color, label_color = color_resolve( p['default'] ) - if p['value'] != '': # parameter previously set + if p.get('value','') != '': # parameter previously set default_color, label_color = color_resolve( p['value'] ) if "none" in title: this_sizer = wx.BoxSizer( wx.HORIZONTAL ) @@ -648,8 +651,8 @@ p['wxId'] = [btn_colour.GetId(),] btn_colour.Bind(csel.EVT_COLOURSELECT, self.OnColorChange ) if "none" in title: - none_check = wx.CheckBox(which_panel, wx.ID_ANY, "Transparent") - if p['value'] != '' and p['value'][0] == "none": + none_check = wx.CheckBox(which_panel, wx.ID_ANY, _("Transparent") ) + if p.get('value','') != '' and p.get('value',[''])[0] == "none": none_check.SetValue(True) else: none_check.SetValue(False) @@ -663,12 +666,13 @@ if txt is not None: txt.SetFont( wx.Font( 12, wx.FONTFAMILY_DEFAULT, wx.NORMAL, text_style, 0, '')) - for f in self.task.flags: + visible_flags = [ f for f in self.task.flags if not f.get( 'hidden', 'no' ) == 'yes' ] + for f in visible_flags: which_sizer = tabsizer[ f['guisection'] ] which_panel = tab[ f['guisection'] ] - title = text_beautify(f['description']) + title = text_beautify( f['description'] ) chk = wx.CheckBox(which_panel,-1, label = title, style = wx.NO_BORDER) - if 'value' in f: chk.SetValue(f['value']) + if 'value' in f: chk.SetValue( f['value'] ) chk.SetFont( wx.Font( 12, wx.FONTFAMILY_DEFAULT, wx.NORMAL, text_style, 0, '')) which_sizer.Add( chk, 0, wx.EXPAND| wx.ALL, 5) f['wxId'] = chk.GetId() @@ -692,7 +696,7 @@ manual_tab.SetMinSize( constrained_size ) self.SetSizer( panelsizer ) - self.hasMain = tab.has_key( 'Main' ) # publish, to enclosing Frame for instance + self.hasMain = tab.has_key( _('Main') ) # publish, to enclosing Frame for instance def OnPageChange(self, event): @@ -701,7 +705,7 @@ def OnColorChange( self, event ): myId = event.GetId() for p in self.task.params: - if type( p['wxId'] ) == type( [] ) and myId in p['wxId']: + if 'wxId' in p and type( p['wxId'] ) == type( [] ) and myId in p['wxId']: has_button = p['wxId'][1] is not None if has_button and wx.FindWindowById( p['wxId'][1] ).GetValue() == True: p[ 'value' ] = 'none' @@ -730,7 +734,7 @@ me = event.GetId() theParam = None for p in self.task.params: - if type( p['wxId'] ) == type( [] ) and me in p['wxId']: + if 'wxId' in p and type( p['wxId'] ) == type( [] ) and me in p['wxId']: theParam = p myIndex = p['wxId'].index( me ) # Unpack current value list @@ -754,7 +758,7 @@ myId = event.GetId() me = wx.FindWindowById( myId ) for porf in self.task.params + self.task.flags: - if type( porf[ 'wxId' ] ) == type( 1 ) and porf['wxId'] == myId: + if 'wxId' in porf and type( porf[ 'wxId' ] ) == type( 1 ) and porf['wxId'] == myId: porf[ 'value' ] = me.GetValue() self.updateStatusLine() @@ -773,11 +777,11 @@ if 'value' in flag and flag['value']: cmd += ' -' + flag['name'] for p in self.task.params: - if p['value'] == '' and p['required'] != 'no': - cmd += ' ' + p['name'] + '=' + '' - errStr += "Parameter " + p['name'] + "(" + p['description'] + ") is missing\n" + if p.get('value','') == '' and p.get('required','no') != 'no': + cmd += ' ' + p['name'] + '=' + _('') + errStr += _("Parameter %s (%s) is missing\n") % ( p['name'], p['description'] ) errors += 1 - if p['value'] != '' and p['value'] != p['default'] : + if p.get('value','') != '' and p['value'] != p.get('default','') : cmd += ' ' + p['name'] + '=' + p['value'] if errors and not ignoreErrors: self.OnError(errStr) @@ -786,7 +790,7 @@ return cmd def OnError(self, errMsg): - dlg = wx.MessageDialog(self, errMsg, "Error", wx.OK | wx.ICON_ERROR) + dlg = wx.MessageDialog(self, errMsg, _("Error"), wx.OK | wx.ICON_ERROR) dlg.ShowModal() dlg.Destroy() @@ -796,7 +800,7 @@ The DTD must be located in $GISBASE/etx/wx/gui_modules/grass-interface.dtd, otherwise the parser will not succeed.""" - gmpath = os.getenv("GISBASE") + "/etc/wx/gui_modules" + gmpath = os.getenv("GISBASE") + "/etc/wx/gui_modules" cmd = cmd + r' --interface-description' cmdout = os.popen(cmd, "r").read() p = re.compile( '(grass-interface.dtd)') @@ -806,10 +810,11 @@ class GrassGUIApp(wx.App): """Stand-alone GRASS command GUI""" - def __init__(self, cmd): - self.grass_task = grassTask() - handler = processTask(self.grass_task) - xml.sax.parseString( getInterfaceDescription( cmd ) , handler ) + def __init__(self, grass_task): + self.grass_task = grass_task +#XXX from pprint import pprint +# pprint( self.grass_task.params ) + wx.App.__init__(self) def OnInit(self): @@ -841,7 +846,7 @@ self.parent = parentframe if len(cmdlst) > 1: - raise ValueError, "usage: %s " % cmdlst[0] + raise ValueError, _("usage: %s ") % cmdlst[0] else: # parse the interface decription self.grass_task = grassTask() @@ -856,11 +861,79 @@ self.mf = mainFrame(self.parent ,-1, self.grass_task, get_dcmd, layer) self.mf.Show(True) + if __name__ == "__main__": if len(sys.argv) == 1: - print "Usage: %s " % sys.argv[0] + print _("usage: %s ") % sys.argv[0] sys.exit() - app = GrassGUIApp(sys.argv[1]) - app.MainLoop() - + if sys.argv[1] != 'test': + grass_task = grassTask() + handler = processTask(grass_task) + xml.sax.parseString( getInterfaceDescription( sys.argv[1] ) , handler ) + app = GrassGUIApp( grass_task ) + app.MainLoop() + else: #Test + task = grassTask() + task.name = "TestTask" + task.description = "This is a artificial grassTask() object intended for testing purposes" + task.params = [ + { + "name" : "text", + "description" : "Enter some text" + },{ + "name" : "hidden_text", + "description" : "This text should not appear in the form", + "hidden" : "yes" + },{ + "name" : "text_default", + "description" : "Enter text to override the default", + "default" : "default text" + },{ + "name" : "text_prefilled", + "description" : "You should see a friendly welcome message here", + "value" : "hello, world" + },{ + "name" : "plain_color", + "description" : "This is a plain color, and it is a compulsory parameter", + "required" : "yes", + "gisprompt" : True, + "prompt" : "color" + },{ + "name" : "transparent_color", + "description" : "This color becomes transparent when set to none", + "guisection" : "tab", + "prompt" : "color" + },{ + "name" : "multi", + "description" : "A multiple selection", + 'default': u'red,green,blue', + 'gisprompt': False, + 'guisection': 'tab', + 'multiple': u'yes', + 'type': u'string', + 'value': '', + 'values': ['red', 'green', u'yellow', u'blue', u'purple', u'other'] + },{ + "name" : "single", + "description" : "A single multiple-choice selection", + 'values': ['red', 'green', u'yellow', u'blue', u'purple', u'other'], + "guisection" : "tab" + } + ] + task.flags = [ + { + "name" : "a", + "description" : "Some flag", + "required" : "yes" + },{ + "name" : "b", + "description" : "pre-filled flag", + "value" : True + },{ + "name" : "h", + "description" : "hidden flag", + "hidden" : "yes" + } + ] + GrassGUIApp( task ).MainLoop() From barton at grass.itc.it Sat Apr 14 09:21:36 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Sat Apr 14 09:21:39 2007 Subject: [grass-addons] r500 - trunk/grassaddons/gui/gui_modules Message-ID: <200704140721.l3E7LaRA017394@grass.itc.it> Author: barton Date: 2007-04-14 09:21:25 +0200 (Sat, 14 Apr 2007) New Revision: 500 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py trunk/grassaddons/gui/gui_modules/wxgui_utils.py Log: Querying of multiple raster and vector files enabled. Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-14 06:01:34 UTC (rev 499) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-14 07:21:25 UTC (rev 500) @@ -665,19 +665,20 @@ # querying elif self.mouse["box"] == "query": east,north = self.Pixel2Cell(self.mouse['begin'][0],self.mouse['begin'][1]) - if self.tree.GetSelection(): - layer = self.tree.GetSelection() - type = self.tree.layertype[layer] - dcmd = self.tree.GetPyData(layer)[0] - mapname = None - for item in dcmd.split(' '): - if 'map=' in item: - mapname = item.split('=')[1] + self.Parent.QueryMap(east,north) +# if self.tree.GetSelection(): +# layer = self.tree.GetSelection() +# type = self.tree.layertype[layer] +# dcmd = self.tree.GetPyData(layer)[0] +# mapname = None +# for item in dcmd.split(' '): +# if 'map=' in item: +# mapname = item.split('=')[1] +# +# self.parent.QueryMap(mapname,type,east,north) +# else: +# print "Quering without gis manager not implemented yet" - self.parent.QueryMap(mapname,type,east,north) - else: - print "Quering without gis manager not implemented yet" - # end drag of overlay decoration elif self.dragid != None: self.ovlcoords[self.dragid] = self.pdc.GetIdBounds(self.dragid) @@ -911,17 +912,20 @@ Set display geometry to match extents in saved region file """ + dlg = wx.MessageDialog(self, 'This is not yet functional', + 'Zoom to saved region extents', wx.OK | wx.ICON_INFORMATION) + dlg.ShowModal() + dlg.Destroy() - print 'not yet functional' - pass - def SaveDisplayRegion(self, event): """ Save display extents to named region file. """ - print 'not yet functional' - pass + dlg = wx.MessageDialog(self, 'This is not yet functional', + 'Save display extents to named region', wx.OK | wx.ICON_INFORMATION) + dlg.ShowModal() + dlg.Destroy() # tmpreg = os.getenv("GRASS_REGION") # os.unsetenv("GRASS_REGION") @@ -987,8 +991,6 @@ # self.SetClientSize((600, 475)) - # Set variables to associate display with GIS Manager page - self.disp_idx = idx # # Fancy gui @@ -1254,7 +1256,10 @@ if dlg.ShowModal() == wx.ID_OK: # data = dlg.GetPrintDialogData() - print 'printing not yet enabled' + dlg = wx.MessageDialog(self, 'This is not yet functional', + 'Map printing', wx.OK | wx.ICON_INFORMATION) + dlg.ShowModal() + dlg.Destroy() # self.log.WriteText('GetAllPages: %d\n' % data.GetAllPages()) dlg.Destroy() @@ -1292,25 +1297,58 @@ # change the cursor self.MapWindow.SetCursor (self.cursors["cross"]) - def QueryMap(self,mapname,type,x,y): + def QueryMap(self,x,y): """ Run *.what command in gis manager output window """ #set query snap distance for v.what at mapunit equivalent of 10 pixels qdist = 10.0 * ((self.Map.region['e'] - self.Map.region['w'])/self.Map.width) - if type == "raster": - cmd = "r.what -f input=%s east_north=%f,%f" %\ - (mapname, float(x), float(y)) + if self.tree.GetSelections(): + mapname = None + raststr = '' + vectstr = '' + rcmd = '' + vcmd = '' + for layer in self.tree.GetSelections(): + type = self.tree.layertype[layer] + dcmd = self.tree.GetPyData(layer)[0] + if type in ('raster', 'rgb', 'his'): + for item in dcmd.split(' '): + if 'map=' in item: + raststr += "%s," % item.split('=')[1] + elif 'red=' in item: + raststr += "%s," % item.split('=')[1] + elif 'h_map=' in item: + raststr += "%s," % item.split('=')[1] + elif type in ('vector', 'thememap', 'themechart'): + for item in dcmd.split(' '): + if 'map=' in item: + vectstr += "%s," % item.split('=')[1] - elif type == "vector": - cmd = "v.what -a map=%s east_north=%f,%f distance=%f" %\ - (mapname, float(x), float(y), qdist) + # build query commands for any selected rasters and vectors + if raststr != '': + raststr = raststr.rstrip(',') + rcmd = "r.what -f input=%s east_north=%f,%f" %\ + (raststr, float(x), float(y)) + if vectstr != '': + vectstr = vectstr.rstrip(',') + vcmd = "v.what -a map=%s east_north=%f,%f distance=%f" %\ + (vectstr, float(x), float(y), qdist) + else: + dlg = wx.MessageDialog(self, 'You must select a map in the GIS Manager to query', + 'Nothing to query', wx.OK | wx.ICON_INFORMATION) + dlg.ShowModal() + dlg.Destroy() + return + # parse query command(s) if self.gismanager: - self.gismanager.goutput.runCmd(cmd) + if rcmd != '': self.gismanager.goutput.runCmd(rcmd) + if vcmd != '': self.gismanager.goutput.runCmd(vcmd) else: - os.system(cmd) + os.system(rcmd) + os.system(vcmd) # toolBar button handlers def onDecoration(self, event): Modified: trunk/grassaddons/gui/gui_modules/wxgui_utils.py =================================================================== --- trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-14 06:01:34 UTC (rev 499) +++ trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-14 07:21:25 UTC (rev 500) @@ -165,6 +165,7 @@ self.Bind(wx.EVT_TREE_DELETE_ITEM, self.onDeleteLayer) self.Bind(wx.EVT_TREE_BEGIN_DRAG, self.onBeginDrag) self.Bind(wx.EVT_TREE_END_DRAG, self.onEndDrag) + self.Bind(wx.EVT_CLOSE, self.onCloseWindow) def AddLayer(self, type): self.first = True @@ -548,6 +549,9 @@ def setNotebookPage(self,pg): self.Parent.notebook.SetSelection(pg) + def onCloseWindow(self, event): + self.Map.Clean() + class TreeCtrlComboPopup(wx.combo.ComboPopup): """ Create a tree ComboBox for selecting maps and other GIS elements @@ -685,7 +689,6 @@ evt.Skip() - class GMConsole(wx.Panel): """ Create and manage output console for commands entered on the @@ -870,8 +873,11 @@ self.cmd_output.write('East: '+rastqlist[0]+"\n") self.cmd_output.write('North: '+rastqlist[1]+"\n") self.cmd_output.write(rastqlist[2]+"\n") - self.cmd_output.write('Category: '+rastqlist[3]+"\n") - self.cmd_output.write('Label: '+rastqlist[4]+"\n") + data = rastqlist[3:] + print 'data=',data + for x in range(0,len(data),2): + self.cmd_output.write('Category: '+data[x]+"\n") + self.cmd_output.write('Label: '+data[x+1]+"\n") else: self.cmd_output.write(oline+"\n") print >> sys.stderr, oline From barton at grass.itc.it Sat Apr 14 10:34:34 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Sat Apr 14 10:34:36 2007 Subject: [grass-addons] r501 - in trunk/grassaddons/gui: . gui_modules Message-ID: <200704140834.l3E8YY6n017490@grass.itc.it> Author: barton Date: 2007-04-14 10:34:20 +0200 (Sat, 14 Apr 2007) New Revision: 501 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py trunk/grassaddons/gui/gui_modules/wxgui_utils.py trunk/grassaddons/gui/wxgui.py Log: Better trash collection Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-14 07:21:25 UTC (rev 500) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-14 08:34:20 UTC (rev 501) @@ -666,18 +666,6 @@ elif self.mouse["box"] == "query": east,north = self.Pixel2Cell(self.mouse['begin'][0],self.mouse['begin'][1]) self.Parent.QueryMap(east,north) -# if self.tree.GetSelection(): -# layer = self.tree.GetSelection() -# type = self.tree.layertype[layer] -# dcmd = self.tree.GetPyData(layer)[0] -# mapname = None -# for item in dcmd.split(' '): -# if 'map=' in item: -# mapname = item.split('=')[1] -# -# self.parent.QueryMap(mapname,type,east,north) -# else: -# print "Quering without gis manager not implemented yet" # end drag of overlay decoration elif self.dragid != None: Modified: trunk/grassaddons/gui/gui_modules/wxgui_utils.py =================================================================== --- trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-14 07:21:25 UTC (rev 500) +++ trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-14 08:34:20 UTC (rev 501) @@ -165,7 +165,7 @@ self.Bind(wx.EVT_TREE_DELETE_ITEM, self.onDeleteLayer) self.Bind(wx.EVT_TREE_BEGIN_DRAG, self.onBeginDrag) self.Bind(wx.EVT_TREE_END_DRAG, self.onEndDrag) - self.Bind(wx.EVT_CLOSE, self.onCloseWindow) +# self.Bind(wx.EVT_CLOSE, self.onCloseWindow) def AddLayer(self, type): self.first = True @@ -550,7 +550,8 @@ self.Parent.notebook.SetSelection(pg) def onCloseWindow(self, event): - self.Map.Clean() + pass +# self.Map.Clean() class TreeCtrlComboPopup(wx.combo.ComboPopup): """ Modified: trunk/grassaddons/gui/wxgui.py =================================================================== --- trunk/grassaddons/gui/wxgui.py 2007-04-14 07:21:25 UTC (rev 500) +++ trunk/grassaddons/gui/wxgui.py 2007-04-14 08:34:20 UTC (rev 501) @@ -263,13 +263,10 @@ Page of notebook closed Also close associated map display """ - closepage = event.GetSelection() - try: - if self.closepage.maptree.mapdisplay.Close(False): - self.closepage.maptree.mapdisplay.Close(True) - except: - pass + self.gm_cb.GetPage(event.GetSelection()).maptree.Map.Clean() + self.gm_cb.GetPage(event.GetSelection()).maptree.Close(True) + event.Skip() def runCmd(self,event): """Run command""" @@ -538,6 +535,8 @@ def onCloseWindow(self, event): '''Cleanup when wxgui.py is quit''' try: + for page in range(self.gm_cb.GetPageCount()): + self.gm_cb.GetPage(page).maptree.Map.Clean() self.DeleteAllPages() except: self.DestroyChildren() From neteler at grass.itc.it Sat Apr 14 10:51:32 2007 From: neteler at grass.itc.it (neteler@grass.itc.it) Date: Sat Apr 14 10:51:34 2007 Subject: [grass-addons] r502 - in trunk/grassaddons/grassflyer/flyer1: . en en/pix Message-ID: <200704140851.l3E8pW3h018353@grass.itc.it> Author: neteler Date: 2007-04-14 10:51:32 +0200 (Sat, 14 Apr 2007) New Revision: 502 Added: trunk/grassaddons/grassflyer/flyer1/README trunk/grassaddons/grassflyer/flyer1/en/ trunk/grassaddons/grassflyer/flyer1/en/Makefile trunk/grassaddons/grassflyer/flyer1/en/grassflyer.tex trunk/grassaddons/grassflyer/flyer1/en/leaflet.cls trunk/grassaddons/grassflyer/flyer1/en/pix/ trunk/grassaddons/grassflyer/flyer1/en/pix/OSGeo_CMYK.pdf trunk/grassaddons/grassflyer/flyer1/en/pix/grasslogo_vector.pdf trunk/grassaddons/grassflyer/flyer1/en/pix/isodist.png trunk/grassaddons/grassflyer/flyer1/en/pix/ndvi.png trunk/grassaddons/grassflyer/flyer1/en/pix/trento3d.pdf trunk/grassaddons/grassflyer/flyer1/en/pix/visibility.png Removed: trunk/grassaddons/grassflyer/flyer1/Makefile trunk/grassaddons/grassflyer/flyer1/en/pix/OSGeo_CMYK.pdf trunk/grassaddons/grassflyer/flyer1/en/pix/grasslogo_vector.pdf trunk/grassaddons/grassflyer/flyer1/en/pix/isodist.png trunk/grassaddons/grassflyer/flyer1/en/pix/ndvi.png trunk/grassaddons/grassflyer/flyer1/en/pix/trento3d.pdf trunk/grassaddons/grassflyer/flyer1/en/pix/visibility.png trunk/grassaddons/grassflyer/flyer1/grassflyer.tex trunk/grassaddons/grassflyer/flyer1/leaflet.cls trunk/grassaddons/grassflyer/flyer1/pix/ Log: moved into en for english Deleted: trunk/grassaddons/grassflyer/flyer1/Makefile =================================================================== --- trunk/grassaddons/grassflyer/flyer1/Makefile 2007-04-14 08:34:20 UTC (rev 501) +++ trunk/grassaddons/grassflyer/flyer1/Makefile 2007-04-14 08:51:32 UTC (rev 502) @@ -1,19 +0,0 @@ -vectoreps := $(wildcard *.eps) -vectorfinal := $(patsubst %.eps,%.pdf,$(vectoreps)) - -default: grassflyer.pdf - -$(vectorfinal): $(vectoreps) - eps2eps -sOutputFile=- $< | epstopdf -f - -%.pdf: %.tex - pdflatex $< - pdflatex $< - -clean: - rm -f *.aux *.log *.out - -distclean: clean - rm -f *.pdf - -.PHONY: clean distclean default Added: trunk/grassaddons/grassflyer/flyer1/README =================================================================== --- trunk/grassaddons/grassflyer/flyer1/README (rev 0) +++ trunk/grassaddons/grassflyer/flyer1/README 2007-04-14 08:51:32 UTC (rev 502) @@ -0,0 +1,3 @@ +GRASS Flyer 1 + +en/ English version Copied: trunk/grassaddons/grassflyer/flyer1/en/Makefile (from rev 501, trunk/grassaddons/grassflyer/flyer1/Makefile) =================================================================== --- trunk/grassaddons/grassflyer/flyer1/en/Makefile (rev 0) +++ trunk/grassaddons/grassflyer/flyer1/en/Makefile 2007-04-14 08:51:32 UTC (rev 502) @@ -0,0 +1,19 @@ +vectoreps := $(wildcard *.eps) +vectorfinal := $(patsubst %.eps,%.pdf,$(vectoreps)) + +default: grassflyer.pdf + +$(vectorfinal): $(vectoreps) + eps2eps -sOutputFile=- $< | epstopdf -f + +%.pdf: %.tex + pdflatex $< + pdflatex $< + +clean: + rm -f *.aux *.log *.out + +distclean: clean + rm -f *.pdf + +.PHONY: clean distclean default Copied: trunk/grassaddons/grassflyer/flyer1/en/grassflyer.tex (from rev 501, trunk/grassaddons/grassflyer/flyer1/grassflyer.tex) =================================================================== --- trunk/grassaddons/grassflyer/flyer1/en/grassflyer.tex (rev 0) +++ trunk/grassaddons/grassflyer/flyer1/en/grassflyer.tex 2007-04-14 08:51:32 UTC (rev 502) @@ -0,0 +1,198 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%GRASS PROMOTION FLYER % +%(c) 2007 GRASS PROMOTION TEAM % +%GNU Free Documentation License % +%Version 1.2 % +%Needs leaflet.cls % +%www.ctan.org/tex-archive/macros/latex/contrib/leaflet/% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%Sometimes printing engines need the 2nd side upside down +%in this case, use tumble (which is default) instead of notumble +%If this causes problems, use notumble +%If you need a foldmark, delete nofoldmark +\documentclass[notumble,a4paper,10pt,nofoldmark]{leaflet} +\usepackage{helvet,courier,xcolor} + +% Set Helvetica as the default font +\renewcommand*\familydefault\sfdefault +% Let LaTeX knows that pictures are found in ./pix +\graphicspath{{pix/}} + +% Setting up things for the captions +\usepackage{caption}[2004/07/16] +\captionsetup{% + font={small,it},% + labelformat=empty,% Leaves out label: ``Figure 1'' + labelsep=none,% + aboveskip=0pt% +} +% Defining a new 'figure' environment for the document +\newenvironment{myfig}[1][0pt plus 1.5ex minus .5ex]{\par\vspace*{#1}\begin{minipage}{\textwidth}\centering}{\end{minipage}} + +% Defining the GRASS homepage +\newcommand{\GRASSurl}{\url{http://grass.itc.it}} + +% Define a color for the URIs +\definecolor{darkblue}{RGB}{0,0,88} + +\usepackage{hyperref} +% Setting up some document info +\hypersetup{% + colorlinks=true,% + urlcolor=darkblue,% Redefine this color to change URIs color + pdfauthor={The GRASS Community},% + pdftitle={GRASS GIS: Efficiency through Freedom \& Transparency},% + pdfsubject={GRASS Promotion Flyer},% + breaklinks=true,% + plainpages=false% +} + +% Title page stuff +\title{\textbf{\huge GRASS GIS}\\% +\textsl{Efficiency through Freedom \& Transparency}} +\author{The GRASS Community} +\date{\includegraphics[width=\textwidth]{grasslogo_vector}\\[2ex] +\large\GRASSurl} + +\begin{document} + +\maketitle +\thispagestyle{empty}% Necessary to leave out the page number on the first page + +\newpage + +\section{What is GRASS} + +GRASS (Geographic Resources Analysis Support System) is a free and Open Source Software for performing spatial analysis. It consists of more than 350 modules for processing vector (2D/3D), raster and voxel data. Many interfaces to other programs in related domains like geostatistics, databases, mapserver and even other GIS software exist. It is the largest Open Source GIS. It can serve as a Desktop GIS and as the backbone of a complete GIS Infrastructure. + +\section{Where is GRASS used} + +GRASS is used in scientific applications, commercial settings and by public authorities all over the world. GRASS has shown strong potential for solving geospatial problems in numerous situations world-wide. + +\section{History} + +GRASS was originally developed in the beginning of the 1980's by the US Army Construction Engineering Research Laboratories (USA-CERL) and was published as public domain software. When the USA-CERL withdrew from GRASS development, an international developer team took over this work. Since 1999, GRASS has been published as free software under the terms of the GNU General Public Licence. +\begin{myfig}[1.5ex] +\includegraphics[width=0.7\textwidth]{visibility} +\captionof{figure}{Viewshed analysis performed with GRASS} +\end{myfig} + +\section{Open Source Philosophy} + +The Open Source philosophy provides the user the ability to see the source code and structure of the program which offers a great transparency. Users can extend the program for their own needs. Immediate souce code peer review increases the quality. With the help of the extension manager new modules can be created without GRASS package source code. + +\section{Technical Data Sheet} + +\subsection{License} + +GNU General Public License (Free Software Foundation) + +\subsection{Supported platforms} + +GRASS runs on nearly all platforms. It supports GNU/Linux, Posix compliant Unix Systems, MS-Windows and MacOS X. + +\subsection{Design} + +\begin{itemize} +\item Modular +\item Consists of more than 350 modules +\end{itemize} + +\subsection{Programming Languages} + +\begin{itemize} +\item ANSI C +\item GRASS- SWIG interface +\item Python for WebGIS applications +\item Java Version: JGRASS +\end{itemize} + +\subsection{Data Management Capabilities} + +\begin{itemize} +\item Raster / Vector / Voxel data processing +\item 2D / 3D Raster / Vector modelling +\item Image manipulation +\item Vector topology / Network analysis +\item Geostatistics (Interface to R) +\end{itemize} + +\begin{myfig}[1ex] +\includegraphics[width=0.7\textwidth]{trento3d} +\captionof{figure}{A flyby of the city of Trento, Italy} +\end{myfig} + +\section{Supported File Formats} + +GRASS supports nearly all common GIS file formats through the use of the GDAL/OGR library. In addition it supports the Open GIS Consortium's Simple Features. + +\subsection{Vector File formats} +ASCII, ARC/INFO ungenerate, ARC/INFO E00, Arc\-View SHAPE, BIL, DLG (U.S.), DXF, DXF3D, GMT, GPS-ASCII USGS-DEM, IDRISI, MOSS, MapInfo MIF, TIGER, VRML, \dots + +\subsection{Raster File Formats} +ASCII, ARC/GRID, E00, GIF, GMT, TIF, PNG, Vis5D, SURFER (.grd),\dots +\begin{myfig} +\includegraphics[width=0.7\textwidth]{isodist} +\captionof{figure}{Default GUI configuration showing GRASS network analysis capabilites} +\end{myfig} + +\subsection{Image File Formats} + +CEOS (SAR, SRTM, LANDSAT7 etc.), ERDAS LAN / IMG, HDF, LANDSAT TM/MSS, NHAP aerial photos, SAR, SPOT, \dots +\begin{myfig}[1.5ex] +\includegraphics[width=0.7\textwidth]{ndvi} +\captionof{figure}{Image processing capabilities of GRASS} +\end{myfig} + +\subsection{Database support} + +\begin{itemize} +\item PostgreSQL / PostGIS +\item MySQL +\item SQLite +\item ODBC +\item DBF +\end{itemize} + +\subsection{Output} + +\begin{itemize} +\item Modules for creating maps +\item NVIZ for visualization of 2.5D and 3D data (creation of animations \& flybys) +%\item{GMT export} +%item{VRML} +\item VTK, POVray +\item WebGIS via Mapserver, Python, etc. +\end{itemize} + +\subsection{Interoperability to other GIS- related Software} + +\begin{itemize} +\item Quantum GIS (Free Geodata Viewer and more) +\item R- Language (Statistics) +\item Gstat (Geostatistics) +\item UMN Mapserver (Webmapping) +\end{itemize} + +\section{Where to find more information} + +\begin{itemize} +%\begin{flushleft} +\item{Project Website: \\\GRASSurl} +\item{GRASS Wiki: \\\url{http://grass.gdf.hannover.de/wiki}} +\item{GRASS Promotion Team: \\\url{malte@perlomat.de}} +\item{GRASS mailing lists: \\\url{http://grass.itc.it/community/support.php}} +%\end{flushleft} +\end{itemize} + +\vfill +\section{OSGeo} + +GRASS is a founding project of the Open Source Geospatial Foundation which has the aim to create high quality open source geospatial software. For further information visit the OSGeo homepage: +\begin{center} +\includegraphics[width=0.8\textwidth]{OSGeo_CMYK}\\ +\url{http://www.osgeo.org} +\end{center} + +\end{document} Copied: trunk/grassaddons/grassflyer/flyer1/en/leaflet.cls (from rev 501, trunk/grassaddons/grassflyer/flyer1/leaflet.cls) =================================================================== --- trunk/grassaddons/grassflyer/flyer1/en/leaflet.cls (rev 0) +++ trunk/grassaddons/grassflyer/flyer1/en/leaflet.cls 2007-04-14 08:51:32 UTC (rev 502) @@ -0,0 +1,503 @@ +%% +%% This is file `leaflet.cls', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% leaflet.dtx (with options: `class') +%% +%% Copyright (C) 2003, 2004 +%% Rolf Niepraschk, Rolf.Niepraschk@ptb.de +%% Hubert Gaesslein, HubertJG@open.mind.de +%% +%% This work may be distributed and/or modified under the +%% conditions of the LaTeX Project Public License, either version 1.3 +%% of this license or (at your option) any later version. +%% The latest version of this license is in +%% http://www.latex-project.org/lppl.txt +%% and version 1.3 or later is part of all distributions of LaTeX +%% version 2003/12/01 or later. +%% +%% This work has the LPPL maintenance status "author-maintained". +%% +\NeedsTeXFormat{LaTeX2e}[1999/12/01] +\ProvidesClass{leaflet} + [2004/12/22 v1.0d LaTeX document class (JS,WaS,RN,HjG)] +\let\LL@shipout\shipout \let\LL@outputpage\@outputpage +\let\LL@begindvi\@begindvi \let\LL@@end\@@end +\@ifundefined{iflandscape}{\newif\iflandscape}{}% +\@ifundefined{iftumble}{\newif\iftumble}{}% +\newcommand\LL@debug@info[1]{}% +\DeclareOption{dvips}{\PassOptionsToPackage{\CurrentOption}{graphics}} +\DeclareOption{pdftex}{\PassOptionsToPackage{\CurrentOption}{graphics}} +\DeclareOption{vtex}{\PassOptionsToPackage{\CurrentOption}{graphics}} +\DeclareOption{dvipdfm}{\PassOptionsToPackage{\CurrentOption}{graphics}} +\DeclareOption{twoside}{\OptionNotUsed} +\DeclareOption{twocolumn}{\OptionNotUsed} +\DeclareOption{landscape}{\landscapetrue} +\DeclareOption{portrait}{\landscapefalse} +\DeclareOption{debug}{\let\LL@debug@info\typeout} +\DeclareOption{nospecialtricks}{% + \AtEndOfClass{% + \ifLL@combine + \let\immediate\@@@immediate\let\write\@@@write + \let\openout\@@@openout\let\closeout\@@@closeout + \let\special\@@@special\let\@@@exec@outs\relax + \fi}} +\newcommand*\LL@setPaperSize{} +\DeclareOption{a3paper}{\def\LL@setPaperSize{% + \paperwidth=420mm\paperheight=297mm\relax}}% +\@ifdefinable\ifLL@combine{\newif\ifLL@combine} +\DeclareOption{combine}{\LL@combinetrue} +\DeclareOption{nocombine}{\LL@combinefalse} +\newcommand*\LL@selectOutput{} +\DeclareOption{frontside}{\def\LL@selectOutput#1#2{#1}} +\DeclareOption{backside}{\def\LL@selectOutput#1#2{#2}} +\DeclareOption{bothsides}{\def\LL@selectOutput#1#2{#1#2}} +\DeclareOption{tumble}{\tumbletrue} +\DeclareOption{notumble}{\tumblefalse} +\newcommand*\LL@foldmark{} +\DeclareOption{foldmark}{% + \def\LL@foldmark{% + \begingroup + \linethickness{\LenToUnit{\foldmarkrule}}% + \setlength\@tempdima{\paperheight-\LL@tmargin}% + \put(0,\LenToUnit{\@tempdima}){% + \line(0,-1){\LenToUnit{\foldmarklength}}}% + \endgroup}% +} +\DeclareOption{nofoldmark}{\def\LL@foldmark{}}% +\newcommand*\LL@toomanypages[2]{} +\DeclareOption{draft}{\PassOptionsToClass{\CurrentOption}{article}% + \AtEndOfClass{% + \def\LL@toomanypages#1#2{% + \ClassWarningNoLine{leaflet}{#1.\MessageBreak#2}}% + }% +} +\DeclareOption{final}{\PassOptionsToClass{\CurrentOption}{article}% + \AtEndOfClass{% + \ifLL@combine + \def\LL@toomanypages#1#2{% + \ClassError{leaflet}{#1}{#2.}}% + \else + \def\LL@toomanypages#1#2{% + \ClassWarningNoLine{leaflet}{#1.\MessageBreak#2}}% + \fi + }% +} +\DeclareOption*{\PassOptionsToClass{\CurrentOption}{article}} +\PassOptionsToClass{landscape,a4paper}{article} +\ExecuteOptions{tumble,foldmark,bothsides,combine,landscape,final} +\ProcessOptions\relax +\ifLL@combine + \newcommand*\LL@rotate@I{}\newcommand*\LL@rotate@II{}% + \iflandscape + \def\LL@rotate@I#1{#1}% + \iftumble + \def\LL@rotate@II#1{\rotatebox[origin=c]{180}{#1}}% + \else + \def\LL@rotate@II#1{#1}% + \fi + \else + \def\LL@rotate@I#1{\rotatebox[origin=c]{90}{#1}}% + \iftumble + \def\LL@rotate@II#1{\rotatebox[origin=c]{270}{#1}}% + \else + \def\LL@rotate@II#1{\rotatebox[origin=c]{90}{#1}}% + \fi + \fi + \def\@@@pending@outs{}\let\@@@immediate\immediate + \let\@@@write\write \let\@@@special\special + \let\@@@openout\openout \let\@@@closeout\closeout + \def\immediate{% + \let\write\immediate@write% + \let\openout\immediate@openout% + \let\closeout\immediate@closeout% + \let\special\immediate@special}% + \def\reset@immediate{% + \let\write\pending@write% + \let\openout\pending@openout% + \let\closeout\pending@closeout% + \let\special\@@@special}% + \long\def\pending@write#1#{\pending@@write{#1}} + \def\immediate@write{% + \reset@immediate\@@@immediate\@@@write}% + \def\immediate@openout{% + \reset@immediate\@@@immediate\@@@openout}% + \def\immediate@closeout{% + \reset@immediate\@@@immediate\@@@closeout}% + \def\immediate@special{% + \reset@immediate\@@@immediate\@@@special}% + \let\write\pending@write + \let\openout\pending@openout + \let\closeout\pending@closeout + \def\@dummy@whatsit{\special{}} + \begingroup\@ifundefined{pdfoutput}% + {\endgroup} + {\endgroup + \ifnum\pdfoutput>\z@\def\@dummy@whatsit{\pdfliteral{}}\fi} + \begingroup\expandafter\expandafter\expandafter\endgroup + \expandafter\ifx\csname eTeXversion\endcsname\relax + %%% Test is from Markus Kohm (d.c.t.t, 29 Jun 2004) + \ClassWarningNoLine{leaflet}{% + *************************************\MessageBreak + * It's very recommended to use eTeX \MessageBreak + * with this package! \MessageBreak + *************************************}% + \long\def\pending@@write#1#2{% + \@dummy@whatsit + \g@addto@macro\@@@pending@outs{\@@@immediate\@@@write\number#1{#2},}}% + \def\pending@openout#1 {% + \@dummy@whatsit + \g@addto@macro\@@@pending@outs{\@@@immediate\@@@openout\number#1,}}% + \def\pending@closeout#1{% + \@dummy@whatsit + \g@addto@macro\@@@pending@outs{\@@@immediate\@@@closeout\number#1,}}% + \newcommand*\@@@exec@outs{% + \@@@pending@outs\gdef\@@@pending@outs{}% + \LL@debug@info{% + >>> execute the output commands of the current page <<<}}% + \else + \RequirePackage{etex} + \globmarks\@@@out@mark + \newcounter{@@total@outs}\setcounter{@@total@outs}{0} + \newcounter{@@last@exec}\setcounter{@@last@exec}{0} + \long\def\pending@@write#1#2{% + \global\advance\c@@@total@outs\@ne% + \marks\@@@out@mark{\the\c@@@total@outs}% + \g@addto@macro\@@@pending@outs{\@@@immediate\@@@write\number#1{#2},}}% +\def\pending@openout#1 {% + \global\advance\c@@@total@outs\@ne% + \marks\@@@out@mark{\the\c@@@total@outs}% + \g@addto@macro\@@@pending@outs{\@@@immediate\@@@openout\number#1,}}% +\def\pending@closeout#1{% + \global\advance\c@@@total@outs\@ne% + \marks\@@@out@mark{\the\c@@@total@outs}% + \g@addto@macro\@@@pending@outs{\@@@immediate\@@@closeout\number#1,}}% + \newcommand*\@@@exec@outs{% + \begingroup + \@tempcntb\c@@@total@outs\advance\@tempcntb-\c@@@last@exec% + \edef\reserved@a{\botmarks\@@@out@mark}% + \ifx\reserved@a\@empty\@tempcnta\z@\else\@tempcnta\reserved@a\fi% + \LL@debug@info{PENDING-OUTS:\the\@tempcntb\space\space + TOTAL-OUTS:\the\c@@@total@outs\space\space + LAST-EXEC:\the\c@@@last@exec\space\space + TOPMARK:\topmarks\@@@out@mark\space\space + FIRSTMARK:\firstmarks\@@@out@mark\space\space + BOTMARK:\botmarks\@@@out@mark}% + \advance\@tempcnta-\c@@@total@outs \advance\@tempcntb\@tempcnta + \@tempcnta-\@tempcnta% + \ifnum\@tempcnta>\z@ + \LL@debug@info{% + >>> resave \the\@tempcnta\space output command(s). + Too early to execute! <<<}% + \fi + \@tempcnta\z@ \def\reserved@b{}% + \@for\reserved@a :=\@@@pending@outs\do{% + \ifx\reserved@a\@empty\else + \ifnum\@tempcnta<\@tempcntb% + \reserved@a% execute output's related to the current page box. + \global\advance\c@@@last@exec\@ne + \LL@debug@info{>>> execute output command number + \the\c@@@last@exec\space<<<}% + \else + \expandafter\g@addto@macro\expandafter\reserved@b\expandafter{% + \reserved@a,}% + \fi + \advance\@tempcnta\@ne% + \fi}% + \expandafter\@temptokena\expandafter{\reserved@b}% + \xdef\@@@pending@outs{\the\@temptokena}% + \endgroup}% + \fi% end of eTeX test. + \long\def\protected@write#1#2#3{% + \begingroup + \let\thepage\relax + #2% + \let\protect\@unexpandable@protect + \edef\reserved@a{\noexpand\write#1{#3}}% + \reserved@a% + \endgroup + \if@nobreak\ifvmode\nobreak\fi\fi}% + \def\shipout{\deadcycles\z@\setbox\@tempboxa=} + \let\@begindvi\@empty +\fi% end of \ifLL@combine +\LoadClass{article} +\RequirePackage{everyshi,calc,graphicx} +\newcommand*\LL@pagesize@specials[2]{} +\@ifundefined{Gin@driver}{}% +{% + \ifx\Gin@driver\@empty\else% + \filename@parse{\Gin@driver}\@tempswafalse% + \def\reserved@a{dvips}% + \ifx\filename@base\reserved@a\@tempswatrue\fi% + \def\reserved@a{dvipdfm}% + \ifx\filename@base\reserved@a\@tempswatrue\fi% + \if@tempswa% + \ClassInfo{leaflet}{Generating code for dvips}% + \def\LL@pagesize@specials#1#2{% + \@tempdima=#1\@tempdimb=#2% + \AtBeginDvi{\special{papersize=\the\@tempdima,\the\@tempdimb}}}% + \fi% + \def\reserved@a{pdftex}% + \ifx\filename@base\reserved@a + \ClassInfo{leaflet}{Generating code for pdfTeX}% + \def\LL@pagesize@specials#1#2{% + \@tempdima=#1\@tempdimb=#2% + \pdfpagewidth\@tempdima\pdfpageheight\@tempdimb}% + \fi% + \def\reserved@a{vtex}% + \ifx\filename@base\reserved@a + \ClassInfo{leaflet}{Generating code for VTeX}% + \def\LL@pagesize@specials#1#2{% + \@tempdima=#1\@tempdimb=#2% + \mediawidth\@tempdima\mediaheight\@tempdimb}% + \fi% + \fi +} +\newcommand*\LL@CmdIgnored[1]{% + \ClassWarning{leaflet}{% + `\string#1' ignored}} +\setlength{\parskip}{1ex plus 2pt} +\@listi% +\setlength{\labelwidth}{\leftmargin} +\addtolength{\labelwidth}{-\labelsep} +\pagestyle{empty} +\headheight\z@ +\headsep\z@ +\footskip\z@ +\marginparwidth\z@ +\marginparsep\z@ +\sloppy +\setcounter{secnumdepth}{0} +\renewcommand\twocolumn[1][]{\LL@CmdIgnored{\twocolumn}} +\renewcommand\onecolumn{\LL@CmdIgnored{\onecolumn}} +\renewcommand\topfraction{0.7} +\renewcommand\bottomfraction{0.7} +\setlength{\textfloatsep}{10pt plus 4pt minus 3pt} +\setlength{\parindent}{\z@} +\setlength{\leftmargini}{1.5em} +\setlength{\leftmarginii}{1.5em} +\setlength{\leftmarginiii}{1.5em} +\setlength{\leftmarginiv}{1.5em} +\setlength{\leftmarginv}{1.5em} +\setlength{\leftmarginvi}{1.5em} +\setlength{\labelsep}{.5em} +\setlength \labelwidth{\leftmargini} +\addtolength\labelwidth{-\labelsep} +\def\noparskip{\par\vspace{-\parskip}} +\let\old@small\small +\renewcommand{\small}{\old@small\let\@listi\@listI} +\let\old@footnotesize\footnotesize +\renewcommand{\footnotesize}{\old@footnotesize\let\@listi\@listI} +\newcommand{\sectfont}{\bfseries} +\renewcommand\section{\@startsection{section}{1}{\z@}% + {-3.5ex \@plus -.75ex}% + {1ex} %{1.5ex}% + {\normalfont\large\sectfont}} +\renewcommand\subsection{\@startsection{subsection}{2}{\z@}% + {-2.5ex plus -.5ex}% + {1\p@} %{1ex}% + {\normalfont\normalsize\sectfont}} +\renewcommand\subsubsection{\@startsection{subsubsection}{3}{\z@}% + {-2.5ex plus -.5ex}% + {-1em}% + {\normalfont\normalsize\sectfont}} +\def\part{\LL@CmdIgnored{\part}\secdef\@part\@spart} +\def\@part[#1]#2{} +\def\@spart#1{} + +\renewcommand*\descriptionlabel[1]{% + \hspace\labelsep\normalfont\descfont #1} +\newcommand*\descfont{\bfseries} +\iffalse +\g@addto@macro\enumerate{\parsep2\p@\@plus2\p@\@minus\z@} +\g@addto@macro\itemize{\parsep2\p@\@plus2\p@\@minus\z@} +\g@addto@macro\description{\parsep2\p@\@plus2\p@\@minus\z@} +\else +\newcommand*\LL@listsetup{% + \parsep1ex\@plus.5ex\@minus.25ex% + \LL@debug@info{***parsep=\the\parsep}% + \itemsep\z@ + \LL@debug@info{***itemsep=\the\itemsep}% + \topsep\z@ + \LL@debug@info{***topsep=\the\topsep}% + \LL@debug@info{***partopsep=\the\partopsep}% +} +\def\enumerate{% + \ifnum \@enumdepth >\thr@@\@toodeep\else + \advance\@enumdepth\@ne + \edef\@enumctr{enum\romannumeral\the\@enumdepth}% + \expandafter + \list + \csname label\@enumctr\endcsname + {\usecounter\@enumctr + \def\makelabel##1{\hss\llap{##1}}% + %\def\makelabel##1{##1\hfill}% + %\def\makelabel##1{\hss##1}% + \LL@listsetup + }% + \fi} +\def\itemize{% + \ifnum \@itemdepth >\thr@@\@toodeep\else + \advance\@itemdepth\@ne + \edef\@itemitem{labelitem\romannumeral\the\@itemdepth}% + \expandafter + \list + \csname\@itemitem\endcsname + {% + \def\makelabel##1{\hss\llap{##1}}% + %\def\makelabel##1{##1\hfill}% + %\def\makelabel##1{\hss##1}% + \LL@listsetup + }% + \fi} +\renewenvironment{description} + {\list{}{\labelwidth\z@ \itemindent-\leftmargin + \let\makelabel\descriptionlabel + \LL@listsetup}} + {\endlist} +\fi +\newcommand*\setmargins[4]{% + \setlength\topmargin{#1}% + \edef\LL@tmargin{\the\topmargin}% + \setlength\evensidemargin{#2}% + \setlength\textheight{% + \paperheight-\topmargin-\evensidemargin% + -\headheight-\headsep-\footskip}% + \setlength\oddsidemargin{#3}% + \setlength\evensidemargin{#4}% + \setlength\textwidth{% + \paperwidth-\oddsidemargin-\evensidemargin-\marginparwidth-\marginparsep}% + \addtolength\topmargin{-1in}% + \addtolength\oddsidemargin{-1in}% + \evensidemargin\oddsidemargin% +} +\LL@setPaperSize +\paperwidth=0.333333334\paperwidth +\setmargins{11mm}{11mm}{8mm}{8mm} +\newcommand*\foldmarkrule{0.4pt} +\newcommand*\foldmarklength{2mm} +\newcommand\AddToBackground{% + \@ifstar{\@tempswatrue\LL@AddToBackground} + {\@tempswafalse\LL@AddToBackground}} +\@onlypreamble\AddToBackground +\newcommand\LL@AddToBackground[2]{% + \if@tempswa\def\@tempa{LL@largePic}\else\def\@tempa{LL@smallPic}\fi + \expandafter\providecommand\csname\@tempa\@Roman{#1}\endcsname{}% + \expandafter\g@addto@macro\csname\@tempa\@Roman{#1}\endcsname{#2}} +\newcommand\LenToUnit[1]{#1\@gobble} +\newcommand*\CutLine{% + \@ifstar{\@tempswatrue\LL@CutLine}{\@tempswafalse\LL@CutLine}} +\@onlypreamble\CutLine +\newcommand*\LL@CutLine[1]{% + \ifLL@combine + \ifx\Scissors\@empty\@tempswatrue\fi + \if@tempswa + \AddToBackground{#1}{% + \put(0,0){% + \rotatebox{90}{\makebox(\LenToUnit{\paperheight},0){% + \normalsize + \dotfill}}}}% + \else + \AddToBackground{#1}{% + \put(0,0){% + \rotatebox{90}{\makebox(\LenToUnit{\paperheight},0){% + \normalsize + \dotfill\Scissors\dotfill\dotfill\Scissors\dotfill}}}}% + \fi + \fi} +\IfFileExists{pifont.sty} + {\RequirePackage{pifont}% + \newcommand*\Scissors{\raisebox{-0.85ex}{\large\ding{34}}}}% + {\newcommand*\Scissors{}} +\AddToBackground{3}{\LL@foldmark} +\providecommand*\vb@xt@{\vbox to} +\AtBeginDocument{\EveryShipout{\LL@savePage}} +\newcounter{LL@page}\setcounter{LL@page}{1} +\newcommand\LL@tempa{} +\newcommand*\LL@savePage{% + \ifnum\c@LL@page<7\relax + \setbox\@cclv\vbox{% + \vbox{\@tempdima=1in\relax + \@tempdimb=\paperheight\advance\@tempdimb-\@tempdima + \pictur@(0,0)(\LenToUnit{\@tempdima},\LenToUnit{\@tempdimb})% + \begingroup + \set@typeset@protect + \@nameuse{LL@smallPic\Roman{LL@page}}% + %\set@display@protect + \endgroup + \endpicture}% + \nointerlineskip\box\@cclv}% + \ifLL@combine + \@@@exec@outs + \expandafter\newsavebox\csname LL@box\Roman{LL@page}\endcsname% + \setbox\@cclv=\vbox{\vskip1in\unvbox\@cclv}% + \setbox\@cclv=\vbox{\moveright1in\box\@cclv}% + \setbox\@cclv=\hb@xt@\paperwidth{\box\@cclv\hss}% + \setbox\@cclv=\vb@xt@\paperheight{\box\@cclv\vss}% + \global\expandafter\setbox% + \csname LL@box\Roman{LL@page}\endcsname=\box\@cclv% + \typeout{\@spaces[\the\c@LL@page] ==> [\Roman{LL@page}]}% + \fi + \fi + \ifnum\c@LL@page=7\relax + \begingroup + \set@typeset@protect + \LL@toomanypages{% + The text you supplied fills more than six pages\MessageBreak + and will therefore not fit onto a single flyer}{% + Try using smaller fonts or reducing vertical space}% + \endgroup + \fi + \stepcounter{LL@page}} +\ifLL@combine + \def\@@end{% + \clearpage\pagestyle{empty}% + \let\@outputpage\LL@outputpage + \def\@EveryShipout@Hook{}% + \def\@EveryShipout@AtNextHook{}% + \EveryShipout{\LL@savePage}% + \loop\ifnum\c@LL@page<7\relax + \ClassInfo{leaflet}{Generating empty page \the\c@page}% + \null\newpage + \repeat + \let\shipout\LL@shipout \let\@begindvi\LL@begindvi + \paperwidth=3\paperwidth + \iflandscape + \LL@pagesize@specials{\paperwidth}{\paperheight}% + \else + \LL@pagesize@specials{\paperheight}{\paperwidth}% + \fi + \newcommand*\LL@shipoutPage[1]{% + \let \protect \noexpand + \shipout\vb@xt@\paperheight{% + \set@typeset@protect + \vskip-1in% + \@begindvi\hb@xt@\paperwidth{\hskip-1in##1\hss}\vss}}% + \newcommand*\LL@preparePages[3]{% + \typeout{[\@Roman{##1}\space\@Roman{##2}\space\@Roman{##3}] ==>}% + \pictur@(0,0)\@nameuse{LL@largePic\Roman{page}}\endpicture% + \LL@preparePage{##1}\LL@preparePage{##2}\LL@preparePage{##3}}% + \newcommand*\LL@preparePage[1]{% + \expandafter\box\csname LL@box\@Roman{##1}\endcsname}% + \LL@selectOutput + {\setcounter{page}{1}% + \LL@shipoutPage{\LL@rotate@I{\LL@preparePages{5}{6}{1}}}}% + {\setcounter{page}{2}% + \LL@shipoutPage{\LL@rotate@II{\LL@preparePages{2}{3}{4}}}}% + \LL@@end + }% +\else + \LL@pagesize@specials{\paperwidth}{\paperheight}% + \AtEndDocument{% + \clearpage\pagestyle{empty}% + \loop\ifnum\c@LL@page<7\relax + \ClassInfo{leaflet}{Generating empty page \the\c@page}% + \null\newpage + \repeat + } +\fi +\endinput +%% +%% End of file `leaflet.cls'. Copied: trunk/grassaddons/grassflyer/flyer1/en/pix (from rev 498, trunk/grassaddons/grassflyer/flyer1/pix) Deleted: trunk/grassaddons/grassflyer/flyer1/en/pix/OSGeo_CMYK.pdf =================================================================== (Binary files differ) Copied: trunk/grassaddons/grassflyer/flyer1/en/pix/OSGeo_CMYK.pdf (from rev 501, trunk/grassaddons/grassflyer/flyer1/pix/OSGeo_CMYK.pdf) =================================================================== (Binary files differ) Deleted: trunk/grassaddons/grassflyer/flyer1/en/pix/grasslogo_vector.pdf =================================================================== (Binary files differ) Copied: trunk/grassaddons/grassflyer/flyer1/en/pix/grasslogo_vector.pdf (from rev 501, trunk/grassaddons/grassflyer/flyer1/pix/grasslogo_vector.pdf) =================================================================== (Binary files differ) Deleted: trunk/grassaddons/grassflyer/flyer1/en/pix/isodist.png =================================================================== (Binary files differ) Copied: trunk/grassaddons/grassflyer/flyer1/en/pix/isodist.png (from rev 501, trunk/grassaddons/grassflyer/flyer1/pix/isodist.png) =================================================================== (Binary files differ) Deleted: trunk/grassaddons/grassflyer/flyer1/en/pix/ndvi.png =================================================================== (Binary files differ) Copied: trunk/grassaddons/grassflyer/flyer1/en/pix/ndvi.png (from rev 501, trunk/grassaddons/grassflyer/flyer1/pix/ndvi.png) =================================================================== (Binary files differ) Deleted: trunk/grassaddons/grassflyer/flyer1/en/pix/trento3d.pdf =================================================================== (Binary files differ) Copied: trunk/grassaddons/grassflyer/flyer1/en/pix/trento3d.pdf (from rev 501, trunk/grassaddons/grassflyer/flyer1/pix/trento3d.pdf) =================================================================== (Binary files differ) Deleted: trunk/grassaddons/grassflyer/flyer1/en/pix/visibility.png =================================================================== (Binary files differ) Copied: trunk/grassaddons/grassflyer/flyer1/en/pix/visibility.png (from rev 501, trunk/grassaddons/grassflyer/flyer1/pix/visibility.png) =================================================================== (Binary files differ) Deleted: trunk/grassaddons/grassflyer/flyer1/grassflyer.tex =================================================================== --- trunk/grassaddons/grassflyer/flyer1/grassflyer.tex 2007-04-14 08:34:20 UTC (rev 501) +++ trunk/grassaddons/grassflyer/flyer1/grassflyer.tex 2007-04-14 08:51:32 UTC (rev 502) @@ -1,198 +0,0 @@ -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%GRASS PROMOTION FLYER % -%(c) 2007 GRASS PROMOTION TEAM % -%GNU Free Documentation License % -%Version 1.2 % -%Needs leaflet.cls % -%www.ctan.org/tex-archive/macros/latex/contrib/leaflet/% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -%Sometimes printing engines need the 2nd side upside down -%in this case, use tumble (which is default) instead of notumble -%If this causes problems, use notumble -%If you need a foldmark, delete nofoldmark -\documentclass[notumble,a4paper,10pt,nofoldmark]{leaflet} -\usepackage{helvet,courier,xcolor} - -% Set Helvetica as the default font -\renewcommand*\familydefault\sfdefault -% Let LaTeX knows that pictures are found in ./pix -\graphicspath{{pix/}} - -% Setting up things for the captions -\usepackage{caption}[2004/07/16] -\captionsetup{% - font={small,it},% - labelformat=empty,% Leaves out label: ``Figure 1'' - labelsep=none,% - aboveskip=0pt% -} -% Defining a new 'figure' environment for the document -\newenvironment{myfig}[1][0pt plus 1.5ex minus .5ex]{\par\vspace*{#1}\begin{minipage}{\textwidth}\centering}{\end{minipage}} - -% Defining the GRASS homepage -\newcommand{\GRASSurl}{\url{http://grass.itc.it}} - -% Define a color for the URIs -\definecolor{darkblue}{RGB}{0,0,88} - -\usepackage{hyperref} -% Setting up some document info -\hypersetup{% - colorlinks=true,% - urlcolor=darkblue,% Redefine this color to change URIs color - pdfauthor={The GRASS Community},% - pdftitle={GRASS GIS: Efficiency through Freedom \& Transparency},% - pdfsubject={GRASS Promotion Flyer},% - breaklinks=true,% - plainpages=false% -} - -% Title page stuff -\title{\textbf{\huge GRASS GIS}\\% -\textsl{Efficiency through Freedom \& Transparency}} -\author{The GRASS Community} -\date{\includegraphics[width=\textwidth]{grasslogo_vector}\\[2ex] -\large\GRASSurl} - -\begin{document} - -\maketitle -\thispagestyle{empty}% Necessary to leave out the page number on the first page - -\newpage - -\section{What is GRASS} - -GRASS (Geographic Resources Analysis Support System) is a free and Open Source Software for performing spatial analysis. It consists of more than 350 modules for processing vector (2D/3D), raster and voxel data. Many interfaces to other programs in related domains like geostatistics, databases, mapserver and even other GIS software exist. It is the largest Open Source GIS. It can serve as a Desktop GIS and as the backbone of a complete GIS Infrastructure. - -\section{Where is GRASS used} - -GRASS is used in scientific applications, commercial settings and by public authorities all over the world. GRASS has shown strong potential for solving geospatial problems in numerous situations world-wide. - -\section{History} - -GRASS was originally developed in the beginning of the 1980's by the US Army Construction Engineering Research Laboratories (USA-CERL) and was published as public domain software. When the USA-CERL withdrew from GRASS development, an international developer team took over this work. Since 1999, GRASS has been published as free software under the terms of the GNU General Public Licence. -\begin{myfig}[1.5ex] -\includegraphics[width=0.7\textwidth]{visibility} -\captionof{figure}{Viewshed analysis performed with GRASS} -\end{myfig} - -\section{Open Source Philosophy} - -The Open Source philosophy provides the user the ability to see the source code and structure of the program which offers a great transparency. Users can extend the program for their own needs. Immediate souce code peer review increases the quality. With the help of the extension manager new modules can be created without GRASS package source code. - -\section{Technical Data Sheet} - -\subsection{License} - -GNU General Public License (Free Software Foundation) - -\subsection{Supported platforms} - -GRASS runs on nearly all platforms. It supports GNU/Linux, Posix compliant Unix Systems, MS-Windows and MacOS X. - -\subsection{Design} - -\begin{itemize} -\item Modular -\item Consists of more than 350 modules -\end{itemize} - -\subsection{Programming Languages} - -\begin{itemize} -\item ANSI C -\item GRASS- SWIG interface -\item Python for WebGIS applications -\item Java Version: JGRASS -\end{itemize} - -\subsection{Data Management Capabilities} - -\begin{itemize} -\item Raster / Vector / Voxel data processing -\item 2D / 3D Raster / Vector modelling -\item Image manipulation -\item Vector topology / Network analysis -\item Geostatistics (Interface to R) -\end{itemize} - -\begin{myfig}[1ex] -\includegraphics[width=0.7\textwidth]{trento3d} -\captionof{figure}{A flyby of the city of Trento, Italy} -\end{myfig} - -\section{Supported File Formats} - -GRASS supports nearly all common GIS file formats through the use of the GDAL/OGR library. In addition it supports the Open GIS Consortium's Simple Features. - -\subsection{Vector File formats} -ASCII, ARC/INFO ungenerate, ARC/INFO E00, Arc\-View SHAPE, BIL, DLG (U.S.), DXF, DXF3D, GMT, GPS-ASCII USGS-DEM, IDRISI, MOSS, MapInfo MIF, TIGER, VRML, \dots - -\subsection{Raster File Formats} -ASCII, ARC/GRID, E00, GIF, GMT, TIF, PNG, Vis5D, SURFER (.grd),\dots -\begin{myfig} -\includegraphics[width=0.7\textwidth]{isodist} -\captionof{figure}{Default GUI configuration showing GRASS network analysis capabilites} -\end{myfig} - -\subsection{Image File Formats} - -CEOS (SAR, SRTM, LANDSAT7 etc.), ERDAS LAN / IMG, HDF, LANDSAT TM/MSS, NHAP aerial photos, SAR, SPOT, \dots -\begin{myfig}[1.5ex] -\includegraphics[width=0.7\textwidth]{ndvi} -\captionof{figure}{Image processing capabilities of GRASS} -\end{myfig} - -\subsection{Database support} - -\begin{itemize} -\item PostgreSQL / PostGIS -\item MySQL -\item SQLite -\item ODBC -\item DBF -\end{itemize} - -\subsection{Output} - -\begin{itemize} -\item Modules for creating maps -\item NVIZ for visualization of 2.5D and 3D data (creation of animations \& flybys) -%\item{GMT export} -%item{VRML} -\item VTK, POVray -\item WebGIS via Mapserver, Python, etc. -\end{itemize} - -\subsection{Interoperability to other GIS- related Software} - -\begin{itemize} -\item Quantum GIS (Free Geodata Viewer and more) -\item R- Language (Statistics) -\item Gstat (Geostatistics) -\item UMN Mapserver (Webmapping) -\end{itemize} - -\section{Where to find more information} - -\begin{itemize} -%\begin{flushleft} -\item{Project Website: \\\GRASSurl} -\item{GRASS Wiki: \\\url{http://grass.gdf.hannover.de/wiki}} -\item{GRASS Promotion Team: \\\url{malte@perlomat.de}} -\item{GRASS mailing lists: \\\url{http://grass.itc.it/community/support.php}} -%\end{flushleft} -\end{itemize} - -\vfill -\section{OSGeo} - -GRASS is a founding project of the Open Source Geospatial Foundation which has the aim to create high quality open source geospatial software. For further information visit the OSGeo homepage: -\begin{center} -\includegraphics[width=0.8\textwidth]{OSGeo_CMYK}\\ -\url{http://www.osgeo.org} -\end{center} - -\end{document} Deleted: trunk/grassaddons/grassflyer/flyer1/leaflet.cls =================================================================== --- trunk/grassaddons/grassflyer/flyer1/leaflet.cls 2007-04-14 08:34:20 UTC (rev 501) +++ trunk/grassaddons/grassflyer/flyer1/leaflet.cls 2007-04-14 08:51:32 UTC (rev 502) @@ -1,503 +0,0 @@ -%% -%% This is file `leaflet.cls', -%% generated with the docstrip utility. -%% -%% The original source files were: -%% -%% leaflet.dtx (with options: `class') -%% -%% Copyright (C) 2003, 2004 -%% Rolf Niepraschk, Rolf.Niepraschk@ptb.de -%% Hubert Gaesslein, HubertJG@open.mind.de -%% -%% This work may be distributed and/or modified under the -%% conditions of the LaTeX Project Public License, either version 1.3 -%% of this license or (at your option) any later version. -%% The latest version of this license is in -%% http://www.latex-project.org/lppl.txt -%% and version 1.3 or later is part of all distributions of LaTeX -%% version 2003/12/01 or later. -%% -%% This work has the LPPL maintenance status "author-maintained". -%% -\NeedsTeXFormat{LaTeX2e}[1999/12/01] -\ProvidesClass{leaflet} - [2004/12/22 v1.0d LaTeX document class (JS,WaS,RN,HjG)] -\let\LL@shipout\shipout \let\LL@outputpage\@outputpage -\let\LL@begindvi\@begindvi \let\LL@@end\@@end -\@ifundefined{iflandscape}{\newif\iflandscape}{}% -\@ifundefined{iftumble}{\newif\iftumble}{}% -\newcommand\LL@debug@info[1]{}% -\DeclareOption{dvips}{\PassOptionsToPackage{\CurrentOption}{graphics}} -\DeclareOption{pdftex}{\PassOptionsToPackage{\CurrentOption}{graphics}} -\DeclareOption{vtex}{\PassOptionsToPackage{\CurrentOption}{graphics}} -\DeclareOption{dvipdfm}{\PassOptionsToPackage{\CurrentOption}{graphics}} -\DeclareOption{twoside}{\OptionNotUsed} -\DeclareOption{twocolumn}{\OptionNotUsed} -\DeclareOption{landscape}{\landscapetrue} -\DeclareOption{portrait}{\landscapefalse} -\DeclareOption{debug}{\let\LL@debug@info\typeout} -\DeclareOption{nospecialtricks}{% - \AtEndOfClass{% - \ifLL@combine - \let\immediate\@@@immediate\let\write\@@@write - \let\openout\@@@openout\let\closeout\@@@closeout - \let\special\@@@special\let\@@@exec@outs\relax - \fi}} -\newcommand*\LL@setPaperSize{} -\DeclareOption{a3paper}{\def\LL@setPaperSize{% - \paperwidth=420mm\paperheight=297mm\relax}}% -\@ifdefinable\ifLL@combine{\newif\ifLL@combine} -\DeclareOption{combine}{\LL@combinetrue} -\DeclareOption{nocombine}{\LL@combinefalse} -\newcommand*\LL@selectOutput{} -\DeclareOption{frontside}{\def\LL@selectOutput#1#2{#1}} -\DeclareOption{backside}{\def\LL@selectOutput#1#2{#2}} -\DeclareOption{bothsides}{\def\LL@selectOutput#1#2{#1#2}} -\DeclareOption{tumble}{\tumbletrue} -\DeclareOption{notumble}{\tumblefalse} -\newcommand*\LL@foldmark{} -\DeclareOption{foldmark}{% - \def\LL@foldmark{% - \begingroup - \linethickness{\LenToUnit{\foldmarkrule}}% - \setlength\@tempdima{\paperheight-\LL@tmargin}% - \put(0,\LenToUnit{\@tempdima}){% - \line(0,-1){\LenToUnit{\foldmarklength}}}% - \endgroup}% -} -\DeclareOption{nofoldmark}{\def\LL@foldmark{}}% -\newcommand*\LL@toomanypages[2]{} -\DeclareOption{draft}{\PassOptionsToClass{\CurrentOption}{article}% - \AtEndOfClass{% - \def\LL@toomanypages#1#2{% - \ClassWarningNoLine{leaflet}{#1.\MessageBreak#2}}% - }% -} -\DeclareOption{final}{\PassOptionsToClass{\CurrentOption}{article}% - \AtEndOfClass{% - \ifLL@combine - \def\LL@toomanypages#1#2{% - \ClassError{leaflet}{#1}{#2.}}% - \else - \def\LL@toomanypages#1#2{% - \ClassWarningNoLine{leaflet}{#1.\MessageBreak#2}}% - \fi - }% -} -\DeclareOption*{\PassOptionsToClass{\CurrentOption}{article}} -\PassOptionsToClass{landscape,a4paper}{article} -\ExecuteOptions{tumble,foldmark,bothsides,combine,landscape,final} -\ProcessOptions\relax -\ifLL@combine - \newcommand*\LL@rotate@I{}\newcommand*\LL@rotate@II{}% - \iflandscape - \def\LL@rotate@I#1{#1}% - \iftumble - \def\LL@rotate@II#1{\rotatebox[origin=c]{180}{#1}}% - \else - \def\LL@rotate@II#1{#1}% - \fi - \else - \def\LL@rotate@I#1{\rotatebox[origin=c]{90}{#1}}% - \iftumble - \def\LL@rotate@II#1{\rotatebox[origin=c]{270}{#1}}% - \else - \def\LL@rotate@II#1{\rotatebox[origin=c]{90}{#1}}% - \fi - \fi - \def\@@@pending@outs{}\let\@@@immediate\immediate - \let\@@@write\write \let\@@@special\special - \let\@@@openout\openout \let\@@@closeout\closeout - \def\immediate{% - \let\write\immediate@write% - \let\openout\immediate@openout% - \let\closeout\immediate@closeout% - \let\special\immediate@special}% - \def\reset@immediate{% - \let\write\pending@write% - \let\openout\pending@openout% - \let\closeout\pending@closeout% - \let\special\@@@special}% - \long\def\pending@write#1#{\pending@@write{#1}} - \def\immediate@write{% - \reset@immediate\@@@immediate\@@@write}% - \def\immediate@openout{% - \reset@immediate\@@@immediate\@@@openout}% - \def\immediate@closeout{% - \reset@immediate\@@@immediate\@@@closeout}% - \def\immediate@special{% - \reset@immediate\@@@immediate\@@@special}% - \let\write\pending@write - \let\openout\pending@openout - \let\closeout\pending@closeout - \def\@dummy@whatsit{\special{}} - \begingroup\@ifundefined{pdfoutput}% - {\endgroup} - {\endgroup - \ifnum\pdfoutput>\z@\def\@dummy@whatsit{\pdfliteral{}}\fi} - \begingroup\expandafter\expandafter\expandafter\endgroup - \expandafter\ifx\csname eTeXversion\endcsname\relax - %%% Test is from Markus Kohm (d.c.t.t, 29 Jun 2004) - \ClassWarningNoLine{leaflet}{% - *************************************\MessageBreak - * It's very recommended to use eTeX \MessageBreak - * with this package! \MessageBreak - *************************************}% - \long\def\pending@@write#1#2{% - \@dummy@whatsit - \g@addto@macro\@@@pending@outs{\@@@immediate\@@@write\number#1{#2},}}% - \def\pending@openout#1 {% - \@dummy@whatsit - \g@addto@macro\@@@pending@outs{\@@@immediate\@@@openout\number#1,}}% - \def\pending@closeout#1{% - \@dummy@whatsit - \g@addto@macro\@@@pending@outs{\@@@immediate\@@@closeout\number#1,}}% - \newcommand*\@@@exec@outs{% - \@@@pending@outs\gdef\@@@pending@outs{}% - \LL@debug@info{% - >>> execute the output commands of the current page <<<}}% - \else - \RequirePackage{etex} - \globmarks\@@@out@mark - \newcounter{@@total@outs}\setcounter{@@total@outs}{0} - \newcounter{@@last@exec}\setcounter{@@last@exec}{0} - \long\def\pending@@write#1#2{% - \global\advance\c@@@total@outs\@ne% - \marks\@@@out@mark{\the\c@@@total@outs}% - \g@addto@macro\@@@pending@outs{\@@@immediate\@@@write\number#1{#2},}}% -\def\pending@openout#1 {% - \global\advance\c@@@total@outs\@ne% - \marks\@@@out@mark{\the\c@@@total@outs}% - \g@addto@macro\@@@pending@outs{\@@@immediate\@@@openout\number#1,}}% -\def\pending@closeout#1{% - \global\advance\c@@@total@outs\@ne% - \marks\@@@out@mark{\the\c@@@total@outs}% - \g@addto@macro\@@@pending@outs{\@@@immediate\@@@closeout\number#1,}}% - \newcommand*\@@@exec@outs{% - \begingroup - \@tempcntb\c@@@total@outs\advance\@tempcntb-\c@@@last@exec% - \edef\reserved@a{\botmarks\@@@out@mark}% - \ifx\reserved@a\@empty\@tempcnta\z@\else\@tempcnta\reserved@a\fi% - \LL@debug@info{PENDING-OUTS:\the\@tempcntb\space\space - TOTAL-OUTS:\the\c@@@total@outs\space\space - LAST-EXEC:\the\c@@@last@exec\space\space - TOPMARK:\topmarks\@@@out@mark\space\space - FIRSTMARK:\firstmarks\@@@out@mark\space\space - BOTMARK:\botmarks\@@@out@mark}% - \advance\@tempcnta-\c@@@total@outs \advance\@tempcntb\@tempcnta - \@tempcnta-\@tempcnta% - \ifnum\@tempcnta>\z@ - \LL@debug@info{% - >>> resave \the\@tempcnta\space output command(s). - Too early to execute! <<<}% - \fi - \@tempcnta\z@ \def\reserved@b{}% - \@for\reserved@a :=\@@@pending@outs\do{% - \ifx\reserved@a\@empty\else - \ifnum\@tempcnta<\@tempcntb% - \reserved@a% execute output's related to the current page box. - \global\advance\c@@@last@exec\@ne - \LL@debug@info{>>> execute output command number - \the\c@@@last@exec\space<<<}% - \else - \expandafter\g@addto@macro\expandafter\reserved@b\expandafter{% - \reserved@a,}% - \fi - \advance\@tempcnta\@ne% - \fi}% - \expandafter\@temptokena\expandafter{\reserved@b}% - \xdef\@@@pending@outs{\the\@temptokena}% - \endgroup}% - \fi% end of eTeX test. - \long\def\protected@write#1#2#3{% - \begingroup - \let\thepage\relax - #2% - \let\protect\@unexpandable@protect - \edef\reserved@a{\noexpand\write#1{#3}}% - \reserved@a% - \endgroup - \if@nobreak\ifvmode\nobreak\fi\fi}% - \def\shipout{\deadcycles\z@\setbox\@tempboxa=} - \let\@begindvi\@empty -\fi% end of \ifLL@combine -\LoadClass{article} -\RequirePackage{everyshi,calc,graphicx} -\newcommand*\LL@pagesize@specials[2]{} -\@ifundefined{Gin@driver}{}% -{% - \ifx\Gin@driver\@empty\else% - \filename@parse{\Gin@driver}\@tempswafalse% - \def\reserved@a{dvips}% - \ifx\filename@base\reserved@a\@tempswatrue\fi% - \def\reserved@a{dvipdfm}% - \ifx\filename@base\reserved@a\@tempswatrue\fi% - \if@tempswa% - \ClassInfo{leaflet}{Generating code for dvips}% - \def\LL@pagesize@specials#1#2{% - \@tempdima=#1\@tempdimb=#2% - \AtBeginDvi{\special{papersize=\the\@tempdima,\the\@tempdimb}}}% - \fi% - \def\reserved@a{pdftex}% - \ifx\filename@base\reserved@a - \ClassInfo{leaflet}{Generating code for pdfTeX}% - \def\LL@pagesize@specials#1#2{% - \@tempdima=#1\@tempdimb=#2% - \pdfpagewidth\@tempdima\pdfpageheight\@tempdimb}% - \fi% - \def\reserved@a{vtex}% - \ifx\filename@base\reserved@a - \ClassInfo{leaflet}{Generating code for VTeX}% - \def\LL@pagesize@specials#1#2{% - \@tempdima=#1\@tempdimb=#2% - \mediawidth\@tempdima\mediaheight\@tempdimb}% - \fi% - \fi -} -\newcommand*\LL@CmdIgnored[1]{% - \ClassWarning{leaflet}{% - `\string#1' ignored}} -\setlength{\parskip}{1ex plus 2pt} -\@listi% -\setlength{\labelwidth}{\leftmargin} -\addtolength{\labelwidth}{-\labelsep} -\pagestyle{empty} -\headheight\z@ -\headsep\z@ -\footskip\z@ -\marginparwidth\z@ -\marginparsep\z@ -\sloppy -\setcounter{secnumdepth}{0} -\renewcommand\twocolumn[1][]{\LL@CmdIgnored{\twocolumn}} -\renewcommand\onecolumn{\LL@CmdIgnored{\onecolumn}} -\renewcommand\topfraction{0.7} -\renewcommand\bottomfraction{0.7} -\setlength{\textfloatsep}{10pt plus 4pt minus 3pt} -\setlength{\parindent}{\z@} -\setlength{\leftmargini}{1.5em} -\setlength{\leftmarginii}{1.5em} -\setlength{\leftmarginiii}{1.5em} -\setlength{\leftmarginiv}{1.5em} -\setlength{\leftmarginv}{1.5em} -\setlength{\leftmarginvi}{1.5em} -\setlength{\labelsep}{.5em} -\setlength \labelwidth{\leftmargini} -\addtolength\labelwidth{-\labelsep} -\def\noparskip{\par\vspace{-\parskip}} -\let\old@small\small -\renewcommand{\small}{\old@small\let\@listi\@listI} -\let\old@footnotesize\footnotesize -\renewcommand{\footnotesize}{\old@footnotesize\let\@listi\@listI} -\newcommand{\sectfont}{\bfseries} -\renewcommand\section{\@startsection{section}{1}{\z@}% - {-3.5ex \@plus -.75ex}% - {1ex} %{1.5ex}% - {\normalfont\large\sectfont}} -\renewcommand\subsection{\@startsection{subsection}{2}{\z@}% - {-2.5ex plus -.5ex}% - {1\p@} %{1ex}% - {\normalfont\normalsize\sectfont}} -\renewcommand\subsubsection{\@startsection{subsubsection}{3}{\z@}% - {-2.5ex plus -.5ex}% - {-1em}% - {\normalfont\normalsize\sectfont}} -\def\part{\LL@CmdIgnored{\part}\secdef\@part\@spart} -\def\@part[#1]#2{} -\def\@spart#1{} - -\renewcommand*\descriptionlabel[1]{% - \hspace\labelsep\normalfont\descfont #1} -\newcommand*\descfont{\bfseries} -\iffalse -\g@addto@macro\enumerate{\parsep2\p@\@plus2\p@\@minus\z@} -\g@addto@macro\itemize{\parsep2\p@\@plus2\p@\@minus\z@} -\g@addto@macro\description{\parsep2\p@\@plus2\p@\@minus\z@} -\else -\newcommand*\LL@listsetup{% - \parsep1ex\@plus.5ex\@minus.25ex% - \LL@debug@info{***parsep=\the\parsep}% - \itemsep\z@ - \LL@debug@info{***itemsep=\the\itemsep}% - \topsep\z@ - \LL@debug@info{***topsep=\the\topsep}% - \LL@debug@info{***partopsep=\the\partopsep}% -} -\def\enumerate{% - \ifnum \@enumdepth >\thr@@\@toodeep\else - \advance\@enumdepth\@ne - \edef\@enumctr{enum\romannumeral\the\@enumdepth}% - \expandafter - \list - \csname label\@enumctr\endcsname - {\usecounter\@enumctr - \def\makelabel##1{\hss\llap{##1}}% - %\def\makelabel##1{##1\hfill}% - %\def\makelabel##1{\hss##1}% - \LL@listsetup - }% - \fi} -\def\itemize{% - \ifnum \@itemdepth >\thr@@\@toodeep\else - \advance\@itemdepth\@ne - \edef\@itemitem{labelitem\romannumeral\the\@itemdepth}% - \expandafter - \list - \csname\@itemitem\endcsname - {% - \def\makelabel##1{\hss\llap{##1}}% - %\def\makelabel##1{##1\hfill}% - %\def\makelabel##1{\hss##1}% - \LL@listsetup - }% - \fi} -\renewenvironment{description} - {\list{}{\labelwidth\z@ \itemindent-\leftmargin - \let\makelabel\descriptionlabel - \LL@listsetup}} - {\endlist} -\fi -\newcommand*\setmargins[4]{% - \setlength\topmargin{#1}% - \edef\LL@tmargin{\the\topmargin}% - \setlength\evensidemargin{#2}% - \setlength\textheight{% - \paperheight-\topmargin-\evensidemargin% - -\headheight-\headsep-\footskip}% - \setlength\oddsidemargin{#3}% - \setlength\evensidemargin{#4}% - \setlength\textwidth{% - \paperwidth-\oddsidemargin-\evensidemargin-\marginparwidth-\marginparsep}% - \addtolength\topmargin{-1in}% - \addtolength\oddsidemargin{-1in}% - \evensidemargin\oddsidemargin% -} -\LL@setPaperSize -\paperwidth=0.333333334\paperwidth -\setmargins{11mm}{11mm}{8mm}{8mm} -\newcommand*\foldmarkrule{0.4pt} -\newcommand*\foldmarklength{2mm} -\newcommand\AddToBackground{% - \@ifstar{\@tempswatrue\LL@AddToBackground} - {\@tempswafalse\LL@AddToBackground}} -\@onlypreamble\AddToBackground -\newcommand\LL@AddToBackground[2]{% - \if@tempswa\def\@tempa{LL@largePic}\else\def\@tempa{LL@smallPic}\fi - \expandafter\providecommand\csname\@tempa\@Roman{#1}\endcsname{}% - \expandafter\g@addto@macro\csname\@tempa\@Roman{#1}\endcsname{#2}} -\newcommand\LenToUnit[1]{#1\@gobble} -\newcommand*\CutLine{% - \@ifstar{\@tempswatrue\LL@CutLine}{\@tempswafalse\LL@CutLine}} -\@onlypreamble\CutLine -\newcommand*\LL@CutLine[1]{% - \ifLL@combine - \ifx\Scissors\@empty\@tempswatrue\fi - \if@tempswa - \AddToBackground{#1}{% - \put(0,0){% - \rotatebox{90}{\makebox(\LenToUnit{\paperheight},0){% - \normalsize - \dotfill}}}}% - \else - \AddToBackground{#1}{% - \put(0,0){% - \rotatebox{90}{\makebox(\LenToUnit{\paperheight},0){% - \normalsize - \dotfill\Scissors\dotfill\dotfill\Scissors\dotfill}}}}% - \fi - \fi} -\IfFileExists{pifont.sty} - {\RequirePackage{pifont}% - \newcommand*\Scissors{\raisebox{-0.85ex}{\large\ding{34}}}}% - {\newcommand*\Scissors{}} -\AddToBackground{3}{\LL@foldmark} -\providecommand*\vb@xt@{\vbox to} -\AtBeginDocument{\EveryShipout{\LL@savePage}} -\newcounter{LL@page}\setcounter{LL@page}{1} -\newcommand\LL@tempa{} -\newcommand*\LL@savePage{% - \ifnum\c@LL@page<7\relax - \setbox\@cclv\vbox{% - \vbox{\@tempdima=1in\relax - \@tempdimb=\paperheight\advance\@tempdimb-\@tempdima - \pictur@(0,0)(\LenToUnit{\@tempdima},\LenToUnit{\@tempdimb})% - \begingroup - \set@typeset@protect - \@nameuse{LL@smallPic\Roman{LL@page}}% - %\set@display@protect - \endgroup - \endpicture}% - \nointerlineskip\box\@cclv}% - \ifLL@combine - \@@@exec@outs - \expandafter\newsavebox\csname LL@box\Roman{LL@page}\endcsname% - \setbox\@cclv=\vbox{\vskip1in\unvbox\@cclv}% - \setbox\@cclv=\vbox{\moveright1in\box\@cclv}% - \setbox\@cclv=\hb@xt@\paperwidth{\box\@cclv\hss}% - \setbox\@cclv=\vb@xt@\paperheight{\box\@cclv\vss}% - \global\expandafter\setbox% - \csname LL@box\Roman{LL@page}\endcsname=\box\@cclv% - \typeout{\@spaces[\the\c@LL@page] ==> [\Roman{LL@page}]}% - \fi - \fi - \ifnum\c@LL@page=7\relax - \begingroup - \set@typeset@protect - \LL@toomanypages{% - The text you supplied fills more than six pages\MessageBreak - and will therefore not fit onto a single flyer}{% - Try using smaller fonts or reducing vertical space}% - \endgroup - \fi - \stepcounter{LL@page}} -\ifLL@combine - \def\@@end{% - \clearpage\pagestyle{empty}% - \let\@outputpage\LL@outputpage - \def\@EveryShipout@Hook{}% - \def\@EveryShipout@AtNextHook{}% - \EveryShipout{\LL@savePage}% - \loop\ifnum\c@LL@page<7\relax - \ClassInfo{leaflet}{Generating empty page \the\c@page}% - \null\newpage - \repeat - \let\shipout\LL@shipout \let\@begindvi\LL@begindvi - \paperwidth=3\paperwidth - \iflandscape - \LL@pagesize@specials{\paperwidth}{\paperheight}% - \else - \LL@pagesize@specials{\paperheight}{\paperwidth}% - \fi - \newcommand*\LL@shipoutPage[1]{% - \let \protect \noexpand - \shipout\vb@xt@\paperheight{% - \set@typeset@protect - \vskip-1in% - \@begindvi\hb@xt@\paperwidth{\hskip-1in##1\hss}\vss}}% - \newcommand*\LL@preparePages[3]{% - \typeout{[\@Roman{##1}\space\@Roman{##2}\space\@Roman{##3}] ==>}% - \pictur@(0,0)\@nameuse{LL@largePic\Roman{page}}\endpicture% - \LL@preparePage{##1}\LL@preparePage{##2}\LL@preparePage{##3}}% - \newcommand*\LL@preparePage[1]{% - \expandafter\box\csname LL@box\@Roman{##1}\endcsname}% - \LL@selectOutput - {\setcounter{page}{1}% - \LL@shipoutPage{\LL@rotate@I{\LL@preparePages{5}{6}{1}}}}% - {\setcounter{page}{2}% - \LL@shipoutPage{\LL@rotate@II{\LL@preparePages{2}{3}{4}}}}% - \LL@@end - }% -\else - \LL@pagesize@specials{\paperwidth}{\paperheight}% - \AtEndDocument{% - \clearpage\pagestyle{empty}% - \loop\ifnum\c@LL@page<7\relax - \ClassInfo{leaflet}{Generating empty page \the\c@page}% - \null\newpage - \repeat - } -\fi -\endinput -%% -%% End of file `leaflet.cls'. From barton at grass.itc.it Sun Apr 15 18:47:58 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Sun Apr 15 18:48:00 2007 Subject: [grass-addons] r503 - trunk/grassaddons/gui Message-ID: <200704151647.l3FGlwYk025342@grass.itc.it> Author: barton Date: 2007-04-15 18:47:49 +0200 (Sun, 15 Apr 2007) New Revision: 503 Added: trunk/grassaddons/gui/icons/ Log: Test silk icons for wxgrass From barton at grass.itc.it Sun Apr 15 18:55:50 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Sun Apr 15 18:55:52 2007 Subject: [grass-addons] r504 - trunk/grassaddons/gui/icons Message-ID: <200704151655.l3FGtoGe025362@grass.itc.it> Author: barton Date: 2007-04-15 18:54:33 +0200 (Sun, 15 Apr 2007) New Revision: 504 Added: trunk/grassaddons/gui/icons/__init__.py trunk/grassaddons/gui/icons/application.png trunk/grassaddons/gui/icons/application_add.png trunk/grassaddons/gui/icons/application_delete.png trunk/grassaddons/gui/icons/application_lightning.png trunk/grassaddons/gui/icons/chart_bar.png trunk/grassaddons/gui/icons/cog_add.png trunk/grassaddons/gui/icons/cross.png trunk/grassaddons/gui/icons/cursor.png trunk/grassaddons/gui/icons/folder_add.png trunk/grassaddons/gui/icons/grid.png trunk/grassaddons/gui/icons/his.png trunk/grassaddons/gui/icons/image_add.png trunk/grassaddons/gui/icons/images.png trunk/grassaddons/gui/icons/information.png trunk/grassaddons/gui/icons/layout_content.png trunk/grassaddons/gui/icons/map.png trunk/grassaddons/gui/icons/map_add.png trunk/grassaddons/gui/icons/map_magnify.png trunk/grassaddons/gui/icons/map_magnify_menu.png trunk/grassaddons/gui/icons/pan.png trunk/grassaddons/gui/icons/picture_save.png trunk/grassaddons/gui/icons/printer.png trunk/grassaddons/gui/icons/rgb.png trunk/grassaddons/gui/icons/table_add.png trunk/grassaddons/gui/icons/tag_green.png trunk/grassaddons/gui/icons/thematic.png trunk/grassaddons/gui/icons/zoom.png trunk/grassaddons/gui/icons/zoom_back.png trunk/grassaddons/gui/icons/zoom_in.png trunk/grassaddons/gui/icons/zoom_out.png Log: Test silk icons for wxgrass Added: trunk/grassaddons/gui/icons/__init__.py =================================================================== --- trunk/grassaddons/gui/icons/__init__.py (rev 0) +++ trunk/grassaddons/gui/icons/__init__.py 2007-04-15 16:54:33 UTC (rev 504) @@ -0,0 +1,7 @@ +""" +Silk icon set, v1.3 +http://www.famfamfam.com/lab/icons/silk/ + +""" +__author__ = "Mark James" + Added: trunk/grassaddons/gui/icons/application.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/application.png ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/application_add.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/application_add.png ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/application_delete.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/application_delete.png ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/application_lightning.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/application_lightning.png ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/chart_bar.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/chart_bar.png ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/cog_add.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/cog_add.png ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/cross.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/cross.png ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/cursor.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/cursor.png ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/folder_add.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/folder_add.png ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/grid.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/grid.png ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/his.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/his.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/image_add.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/image_add.png ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/images.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/images.png ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/information.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/information.png ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/layout_content.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/layout_content.png ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/map.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/map.png ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/map_add.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/map_add.png ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/map_magnify.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/map_magnify.png ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/map_magnify_menu.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/map_magnify_menu.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/pan.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/pan.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/picture_save.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/picture_save.png ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/printer.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/printer.png ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/rgb.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/rgb.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/table_add.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/table_add.png ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/tag_green.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/tag_green.png ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/thematic.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/thematic.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/zoom.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/zoom.png ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/zoom_back.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/zoom_back.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/zoom_in.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/zoom_in.png ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gui/icons/zoom_out.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/icons/zoom_out.png ___________________________________________________________________ Name: svn:executable + * Name: svn:mime-type + application/octet-stream From barton at grass.itc.it Sun Apr 15 18:57:58 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Sun Apr 15 18:58:02 2007 Subject: [grass-addons] r505 - in trunk/grassaddons/gui: . gui_modules Message-ID: <200704151657.l3FGvw7G025384@grass.itc.it> Author: barton Date: 2007-04-15 18:57:42 +0200 (Sun, 15 Apr 2007) New Revision: 505 Added: trunk/grassaddons/gui/gui_modules/toolbars_silk.py trunk/grassaddons/gui/wxgui_silk.py Log: Test silk icon toolbars Added: trunk/grassaddons/gui/gui_modules/toolbars_silk.py =================================================================== --- trunk/grassaddons/gui/gui_modules/toolbars_silk.py (rev 0) +++ trunk/grassaddons/gui/gui_modules/toolbars_silk.py 2007-04-15 16:57:42 UTC (rev 505) @@ -0,0 +1,280 @@ +""" +toolbars package + +class: +* MapToolbar +* DigitToolbar +""" + +import wx +import os, sys +#import wxgui_utils + +import cmd + +#icons= os.path.split(icons)[0] +#icons= os.path.split(icons)[0] +#icons= os.path.split(icons)[0] +#print icons + +#if not os.getenv("GRASS_ICONPATH"): +# icons = os.getenv("GISBASE") + "/etc/gui/icons/" +#else: +# icons = os.environ["GRASS_ICONPATH"] + +import icons +iconpath = icons.__path__[0] +sys.path.append(iconpath) + +class MapToolbar: + """ + Main Map Display toolbar + """ + + def __init__(self, mapdisplay, map): + + global icons + + self.mapcontent = map + self.mapdisplay = mapdisplay + + self.toolbar = wx.ToolBar(parent=self.mapdisplay, id=wx.ID_ANY) + + #self.SetToolBar(self.toolbar) + tsize = (20,20) + self.toolbar.SetToolBitmapSize(tsize) + + # + # Draw + # + + self.displaymap = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="displaymap", + bitmap=wx.Bitmap(os.path.join(iconpath,'application_lightning.png'), wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, kind=wx.ITEM_NORMAL, + shortHelp="Display map", longHelp="") + self.erase = self.toolbar.AddLabelTool(wx.ID_ANY, "erase", + bitmap=wx.Bitmap(os.path.join(iconpath,'application_delete.png'), wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, kind=wx.ITEM_NORMAL, shortHelp="Erase display", longHelp="") + self.toolbar.AddSeparator() + + # + # Zooming, etc. + # + self.pointer = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="pointer", + bitmap=wx.Bitmap(os.path.join(iconpath,'cursor.png'), wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, + kind=wx.ITEM_RADIO, + shortHelp="Pointer", longHelp="") + self.zoomin = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="zoom_in", + bitmap=wx.Bitmap(os.path.join(iconpath,'zoom_in.png'), + wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, kind=wx.ITEM_RADIO, + shortHelp="Zoom in", longHelp="Drag or click mouse to zoom") + self.zoomout = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="zoom_out", + bitmap=wx.Bitmap(os.path.join(iconpath,'zoom_out.png'), + wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, + kind=wx.ITEM_RADIO, + shortHelp="Zoom out", longHelp="Drag or click mouse to unzoom") + self.pan = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="pan", + bitmap=wx.Bitmap(os.path.join(iconpath,'pan.png'), + wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, + kind=wx.ITEM_RADIO, + shortHelp="Pan", longHelp="Drag with mouse to pan") + self.query = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="query", + bitmap=wx.Bitmap(os.path.join(iconpath,'information.png'), + wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, + kind=wx.ITEM_RADIO, + shortHelp="Query", longHelp="Query selected map") + self.toolbar.AddSeparator() + + + self.zoomback = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="zoom_back", + bitmap=wx.Bitmap(os.path.join(iconpath,'zoom_back.png'), + wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, kind=wx.ITEM_NORMAL, + shortHelp="Zoom options", longHelp="Display zoom management") + self.zoommenu = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="zoommenu", + bitmap=wx.Bitmap(os.path.join(iconpath,'map_magnify_menu.png'), + wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, + shortHelp="Decoration", longHelp="Add graphic overlays to map") + self.toolbar.AddSeparator() + + + self.dec = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="dec", + bitmap=wx.Bitmap(os.path.join(iconpath,'layout_content.png'), + wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, + shortHelp="Decoration", + longHelp="Add graphic overlays to map") + + self.toolbar.AddSeparator() + + # + # Misc + # + self.savefile = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="savefile", + #bitmap=wx.Bitmap(os.path.join(icons,"file-save.gif"), + #wx.BITMAP_TYPE_ANY), + # just testing wx.ArtProvider + bitmap=wx.Bitmap(os.path.join(iconpath,'picture_save.png'), wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, + kind=wx.ITEM_NORMAL, + shortHelp="Save display to PNG file", + longHelp="") + + self.printmap = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="printmap", + #bitmap=wx.Bitmap(os.path.join(icons,"file-save.gif"), + #wx.BITMAP_TYPE_ANY), + # just testing wx.ArtProvider + bitmap=wx.Bitmap(os.path.join(iconpath,'printer.png'), wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, kind=wx.ITEM_NORMAL, + shortHelp="Print display", longHelp="") + + self.toolbar.AddSeparator() + + # + # Optional toolbars + # + self.combo = wx.ComboBox(parent=self.toolbar, id=wx.ID_ANY, value='Tools', + choices=['Digitize'], style=wx.CB_READONLY, size=(110, -1)) + + self.comboid = self.toolbar.AddControl(self.combo) + + self.toolbar.Realize() + + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.ReDraw, self.displaymap) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.Pointer, self.pointer) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnZoomIn, self.zoomin) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnZoomOut, self.zoomout) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnPan, self.pan) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnZoomBack, self.zoomback) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.onDecoration, self.dec) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.onZoomMenu, self.zoommenu) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnQuery, self.query) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnErase, self.erase) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.SaveToFile, self.savefile) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.PrintMap, self.printmap) + self.mapdisplay.Bind(wx.EVT_COMBOBOX, self.OnSelect, self.comboid) + + def OnSelect(self,event): + tool = event.GetString() + + if tool == "Digitize" and not self.mapdisplay.digittoolbar: + self.mapdisplay.AddToolbar("digit") + +class DigitToolbar: + """ + Toolbar for digitization + """ + + def __init__(self, parent, map): + + self.mapcontent = map + self.parent = parent + self.icons = os.path.join (icons, "v.digit") + + # selected map to digitize + self.layerID = -1 + # action (digitize new point, line, etc. + self.action = None + # list of available vector maps + self.layers = self._getListOfLayers() + + self.addString = "" + + # create toolbar + self.toolbar = wx.ToolBar(parent=self.parent, id=wx.ID_ANY) + self.toolbar.SetToolBitmapSize(wx.Size(24,24)) + + self.initToolbar() + + def initToolbar(self): + self.combo = wx.ComboBox(self.toolbar, id=wx.ID_ANY, value='Select vector map', + choices=self.layers, size=(150, -1)) + + self.comboid = self.toolbar.AddControl(self.combo) + + self.toolbar.AddSeparator() + + self.point = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="point", + bitmap=wx.Bitmap(os.path.join(self.icons,"new.point.gif"), + wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, + kind=wx.ITEM_RADIO, + shortHelp="Digitize new point", + longHelp="") + + self.line = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="line", + bitmap=wx.Bitmap(os.path.join(self.icons,"new.line.gif"), + wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, + kind=wx.ITEM_RADIO, + shortHelp="Digitize new line", + longHelp="") + + self.boundary = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="boundary", + bitmap=wx.Bitmap(os.path.join(self.icons,"new.boundary.gif"), + wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, + kind=wx.ITEM_RADIO, + shortHelp="Digitize new boundary", + longHelp="") + + self.centroid = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="centroid", + bitmap=wx.Bitmap(os.path.join(self.icons,"new.centroid.gif"), + wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, + kind=wx.ITEM_RADIO, + shortHelp="Digitize new centroid", + longHelp="") + + self.toolbar.AddSeparator() + + self.exit = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="exit", + bitmap=wx.Bitmap(os.path.join(self.icons,"exit.gif"), + wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, + kind=wx.ITEM_NORMAL, + shortHelp="Quit digitization tool", + longHelp="") + + # Bindings + self.parent.Bind(wx.EVT_TOOL, self.OnAddPoint, self.point) + self.parent.Bind(wx.EVT_TOOL, self.OnExit, self.exit) + self.parent.Bind(wx.EVT_COMBOBOX, self.OnSelectMap, self.comboid) + + def OnAddPoint(self,event): + + self.action="addpoint" + #self.parent.MapWindow.mouse['box'] = "point" + + def OnExit (self, event): + """ + Quit digitization tool + """ + + self.parent.RemoveToolbar ("digit") + + def OnSelectMap (self, event): + """ + Select vector map to digitize + + If any vector map is activated for digitization this action + is firstly terminated + """ + + self.layerID = self.combo.GetCurrentSelection() + + # digitize (self.layers[self.layerID], mapset) + + def _getListOfLayers(self): + layers = [] + + for layer in self.mapcontent.GetListOfLayers(l_type="vector"): + layers.append (layer.name) + + return layers Added: trunk/grassaddons/gui/wxgui_silk.py =================================================================== --- trunk/grassaddons/gui/wxgui_silk.py (rev 0) +++ trunk/grassaddons/gui/wxgui_silk.py 2007-04-15 16:57:42 UTC (rev 505) @@ -0,0 +1,587 @@ +#!/usr/bin/env python +""" +Classes: +* GRasterDialog +* GMFrame +* SetVal +* GMApp +""" +import sys +import os +import wx +import wx.combo +import wx.lib.customtreectrl as CT +import wx.lib.flatnotebook as FN +import wx.stc +import wx.richtext + +import sys, os, time, traceback, types + +import wx # This module uses the new wx namespace +import wx.html + + +# try: +# import subprocess +#except: +# from compat import subprocess + +import gui_modules +gmpath = gui_modules.__path__[0] +sys.path.append(gmpath) + +import images +imagepath = images.__path__[0] +sys.path.append(imagepath) + +import icons +iconpath = icons.__path__[0] +sys.path.append(iconpath) + + +import gui_modules.track as track +import gui_modules.wxgui_utils as wxgui_utils +import gui_modules.mapdisp as mapdisp +import gui_modules.render as render +import gui_modules.menudata as menudata +import gui_modules.menuform as menuform +import gui_modules.grassenv as grassenv + +"""Main Python app to set up GIS Manager window and trap commands +Only command console is working currently, but windows for +panels and layer tree done and demo tree items appear""" + +########################################################################## +# +# wxgui.py - wxPython prototype GUI for GRASS 6+ +# +# Authors: Michael Barton (Arizona State University) & +# Jachym Cepicky (Mendel University of Agriculture) +# +# August 2006 +# +# COPYRIGHT: (C) 1999 - 2006 by the GRASS Development Team +# +# This program is free software under the GNU General Public +# License (>=v2). Read the file COPYING that comes with GRASS +# for details. +# +########################################################################## + +menucmd = {} + +class GRasterDialog(wx.Frame): + def __init__(self,parent,id=-1,title="Set raster layer"): + wx.Frame.__init__(self, parent, id , title, size=(50,600)) + + # sizers + sizer = wx.BoxSizer(wx.VERTICAL) + buttsizer = wx.BoxSizer(wx.HORIZONTAL) + + # labels + lmap = wx.StaticText(self,-1,"Map name") + lvalues = wx.StaticText(self,-1,"List of values to be displayed") + lopaque = wx.StaticText(self,-1,"Transparency") + + # checkboxes + cboverlay = wx.CheckBox(self, -1, "Overlay (non-null values)") + cboverlay.SetValue(True) + + # text entries + tmapname = wx.TextCtrl(self,-1,size=(-1,-1)) + tvalues = wx.TextCtrl(self,-1,size=(-1,-1)) + + # buttons + bsize=(75,-1) + bok = wx.Button(self,-1, "OK",size=bsize) + bapply = wx.Button(self,-1, "Apply", size=bsize) + bcancel = wx.Button(self,-1, "Cancel", size=bsize) + + buttsizer.Add(bok, 0, wx.ADJUST_MINSIZE, 1) + buttsizer.Add(bapply, 0, wx.ADJUST_MINSIZE, 1) + buttsizer.Add(bcancel, 0, wx.ADJUST_MINSIZE, 1) + sizer.Add(lopaque,1, wx.EXPAND, 1) + sizer.Add(lmap,0, wx.EXPAND, 1) + sizer.Add(tmapname,0, wx.EXPAND, 1) + sizer.Add(lvalues,0, wx.EXPAND, 1) + sizer.Add(tvalues,0, wx.EXPAND, 1) + sizer.Add(cboverlay,1, wx.EXPAND, 1) + sizer.Add(buttsizer,0, wx.ADJUST_MINSIZE, 1) + self.SetSizer(sizer) + sizer.Fit(self) + self.Layout() + + +class GMFrame(wx.Frame): + '''GIS Manager frame with notebook widget for controlling + GRASS GIS. Includes command console page for typing GRASS + (and other) commands, tree widget page for managing GIS map layers.''' + def __init__(self, parent, id, title): + self.parent = parent + wx.Frame.__init__(self, parent=parent, id=-1, title=title, style=wx.DEFAULT_FRAME_STYLE) + + # creating widgets + self.notebook = self.__createNoteBook() + self.cmdinput = self.__createCommandInput() + self.menubar = self.__createMenuBar() + toolbar = self.__createToolBar() + #self.panel = wx.Panel(self,-1, style= wx.EXPAND) + self.sizer= wx.BoxSizer(wx.VERTICAL) + self.cmdsizer = wx.BoxSizer(wx.HORIZONTAL) + self.track = track + + # do layout + self.SetTitle(_("GRASS GIS Manager - wxPython Prototype")) + self.SetMinSize((450, 450)) + self.SetIcon(wx.Icon(os.path.join(imagepath,'grass.smlogo.gif'), wx.BITMAP_TYPE_ANY)) + # self.nb_panel = wx.Panel(self) + + # initialize variables + self.mapdisplays = {} #dictionary to index open map displays + self.disp_idx = 0 #index value for map displays and layer trees + self.maptree = {} #dictionary to index a layer tree to accompanying a map display + self.mapfocus = 0 #track which display currently has focus + self.curr_page = '' # currently selected page for layer tree notebook + self.curr_pagenum = '' # currently selected page number for layer tree notebook + + self.Bind(wx.EVT_CLOSE, self.onCloseWindow) + self.Bind(wx.EVT_LEFT_DOWN, self.addRaster) + + # item, proportion, flag, border, userData + self.sizer.Add(self.notebook, proportion=1, flag=wx.EXPAND, border=1) + self.sizer.Add(self.cmdinput, proportion=0, flag=wx.EXPAND, border=1) + self.SetSizer(self.sizer) + self.sizer.Fit(self) + self.Layout() + wx.CallAfter(self.notebook.SetSelection, 0) + + # start default initial display + self.newDisplay() + + def __createCommandInput(self): + """Creates command input area""" + #l = wx.StaticText(self, -1, "GRASS> ") + + self.cmdinput = wx.TextCtrl(self, id=wx.ID_ANY, value="", style=wx.HSCROLL|wx.TE_LINEWRAP| + wx.TE_PROCESS_ENTER) + + self.cmdinput.SetFont(wx.Font(10, wx.FONTFAMILY_MODERN, wx.NORMAL, wx.NORMAL, 0, '')) + wx.CallAfter(self.cmdinput.SetInsertionPoint, 0) + + self.Bind(wx.EVT_TEXT_ENTER, self.runCmd, self.cmdinput) + + return self.cmdinput + + def __createMenuBar(self): + """Creates menubar""" + + self.menubar = wx.MenuBar() + menud = menudata.Data() + for eachMenuData in menud.GetMenu(): + for eachHeading in eachMenuData: + menuLabel = eachHeading[0] + menuItems = eachHeading[1] + self.menubar.Append(self.__createMenu(menuItems), menuLabel) + self.SetMenuBar(self.menubar) + + return self.menubar + + def __createMenu(self, menuData): + """Cretes menu""" + + menu = wx.Menu() + for eachItem in menuData: + if len(eachItem) == 2: + label = eachItem[0] + subMenu = self.__createMenu(eachItem[1]) + menu.AppendMenu(wx.NewId(), label, subMenu) + else: + self.__createMenuItem(menu, *eachItem) + return menu + + def __createMenuItem(self, menu, label, help, handler, gcmd, kind=wx.ITEM_NORMAL): + """Creates menu items""" + + if not label: + menu.AppendSeparator() + return + menuItem = menu.Append(-1, label, help, kind) + if label: + menucmd[label] = gcmd + rhandler = eval(handler) + self.Bind(wx.EVT_MENU, rhandler, menuItem) + + def __createNoteBook(self): + """Creates notebook widgets""" + + # create main notebook widget + #bookStyle=FN.FNB_DEFAULT_STYLE #| FN.FNB_FANCY_TABS + #bookStyle=FN.FNB_DEFAULT_STYLE|FN.FNB_BOTTOM|FN.FNB_NO_X_BUTTON|FN.FNB_NO_NAV_BUTTONS + nbStyle=FN.FNB_FANCY_TABS|FN.FNB_BOTTOM|FN.FNB_NO_X_BUTTON|FN.FNB_NO_NAV_BUTTONS + self.notebook = FN.FlatNotebook(self, id=wx.ID_ANY, style=nbStyle) + + # create displays notebook widget and add it to main notebook page + cbStyle=FN.FNB_VC8|FN.FNB_BACKGROUND_GRADIENT|FN.FNB_X_ON_TAB|FN.FNB_TABS_BORDER_SIMPLE + #self.cb_panel = wx.Panel(self,-1, style = wx.EXPAND) + self.gm_cb = FN.FlatNotebook(self, id=wx.ID_ANY, style=cbStyle) + self.gm_cb.SetTabAreaColour(wx.Colour(125,200,175)) + self.notebook.AddPage(self.gm_cb, text="Map layers for each display") + + # create command output text area and add it to main notebook page + #self.outpanel = wx.Panel(self,-1, style = wx.EXPAND) + self.goutput = wxgui_utils.GMConsole(self) + #self.goutput = wx.richtext.RichTextCtrl(self,style=wx.VSCROLL|wx.HSCROLL|wx.NO_BORDER) + self.outpage = self.notebook.AddPage(self.goutput, text="Command output") + + self.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.onCBPageChanged, self.gm_cb) + self.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CLOSING, self.onCBPageClosed, self.gm_cb) + + self.out_sizer = wx.BoxSizer(wx.VERTICAL) + self.out_sizer.Add(self.goutput, proportion=1, flag=wx.EXPAND, border=1) + self.SetSizer(self.out_sizer) + #self.out_sizer.Fit(self.outpage) + #self.outpage.Layout() + + self.Centre() + return self.notebook + + + # choicebook methods + def onCBPageChanged(self, event): + """Page in notebook changed""" + + old_pgnum = event.GetOldSelection() + new_pgnum = event.GetSelection() + self.curr_page = self.gm_cb.GetCurrentPage() + self.curr_pagenum = self.gm_cb.GetSelection() + try: + self.curr_page.maptree.mapdisplay.SetFocus() + self.curr_page.maptree.mapdisplay.Raise() + except: + pass + + event.Skip() + + def onCBPageClosed(self, event): + """ + Page of notebook closed + Also close associated map display + """ + + self.gm_cb.GetPage(event.GetSelection()).maptree.Map.Clean() + self.gm_cb.GetPage(event.GetSelection()).maptree.Close(True) + event.Skip() + + def runCmd(self,event): + """Run command""" + + #global gmpath + cmd = self.cmdinput.GetValue() + + self.goutput.runCmd(cmd) + #menuform.GUI().parseCommand(cmd, gmpath) + + def runMenuCmd(self, event): + """Run menu command""" + + menuitem = self.menubar.FindItemById(event.GetId()) + itemtext = menuitem.GetText() + cmd = menucmd[itemtext] + global gmpath + menuform.GUI().parseCommand(cmd,gmpath, parentframe=self) + + def __createToolBar(self): + """Creates toolbar""" + + toolbar = self.CreateToolBar() + + + for each in self.toolbarData(): + self.addToolbarButton(toolbar, *each) + tsize = (20, 20) + toolbar.SetToolBitmapSize(tsize) + toolbar.Realize() + + def addToolbarButton(self, toolbar, label, icon, help, handler): + """Adds button to the given toolbar""" + + if not label: + toolbar.AddSeparator() + return + tool = toolbar.AddLabelTool(id=wx.ID_ANY, label=label, bitmap=icon, shortHelp=help) + self.Bind(wx.EVT_TOOL, handler, tool) + + def toolbarData(self): + + return ( + ('newdisplay', wx.Bitmap(os.path.join(iconpath,'application_add.png'), wx.BITMAP_TYPE_ANY), 'Start new display', self.newDisplay), + ('', '', '', ''), + ('addrast', wx.Bitmap(os.path.join(iconpath,'image_add.png'), wx.BITMAP_TYPE_ANY), 'Add raster layer', self.onRaster), + ('addvect', wx.Bitmap(os.path.join(iconpath,'map_add.png'), wx.BITMAP_TYPE_ANY), 'Add vector layer', self.onVector), + ('addcmd', wx.Bitmap(os.path.join(iconpath,'cog_add.png'), wx.BITMAP_TYPE_ANY), 'Add command layer', self.addCommand), + ('addgrp', wx.Bitmap(os.path.join(iconpath,'folder_add.png'), wx.BITMAP_TYPE_ANY), 'Add layer group', self.addGroup), + ('addovl', wx.Bitmap(os.path.join(iconpath,'images.png'), wx.BITMAP_TYPE_ANY), 'Add grid or vector labels overlay', self.onOverlay), + ('delcmd', wx.Bitmap(os.path.join(iconpath,'cross.png'), wx.BITMAP_TYPE_ANY), 'Delete selected layer', self.deleteLayer), + ('', '', '', ''), + ('attributetable',wx.Bitmap(os.path.join(iconpath,'table_add.png'),wx.BITMAP_TYPE_ANY), 'Show attribute table', self.ShowAttributeTable), + ) + + def ShowAttributeTable(self,event): + if self.curr_page.maptree.GetSelection() not in self.curr_page.maptree.layertype: return + maptype = self.curr_page.maptree.layertype[self.curr_page.maptree.GetSelection()] + if maptype != 'vector': + print 'Attribute management only available for vector files' + return + + if not self.curr_page.maptree.GetPyData(self.curr_page.maptree.GetSelection()): return + dcmd = self.curr_page.maptree.GetPyData(self.curr_page.maptree.GetSelection())[0] + if not dcmd: return + mapname = map = mapset = size = icon = None + for item in dcmd.split(' '): + if 'map=' in item: + mapname = item.split('=')[1] + elif 'size=' in item: + size = item.split('=')[1] + elif 'icon=' in item: + icon = item.split('=')[1] + + pointdata = (icon,size) + + from gui_modules import dbm + self.dbmanager = gui_modules.dbm.AttributeManager(self, + -1,"GRASS Attribute Table Manager: %s" % mapname, + size=wx.Size(500,300),vectmap=mapname, + pointdata=pointdata) + + + def newDisplay(self, event=None): + """Create new map display frame""" + + # make a new page in the bookcontrol for the layer tree (on page 0 of the notebook) + self.pg_panel = wx.Panel(self.gm_cb, id=wx.ID_ANY, style= wx.EXPAND) + self.gm_cb.AddPage(self.pg_panel, text="Display "+ str(self.disp_idx), select = True) + self.curr_page = self.gm_cb.GetCurrentPage() + + # create layer tree (tree control for managing GIS layers) and put on new notebook page + self.curr_page.maptree = wxgui_utils.LayerTree(self.curr_page, id=wx.ID_ANY, pos=wx.DefaultPosition, + size=wx.DefaultSize, style=wx.TR_HAS_BUTTONS + |wx.TR_LINES_AT_ROOT|wx.TR_EDIT_LABELS|wx.TR_HIDE_ROOT + |wx.TR_DEFAULT_STYLE|wx.NO_BORDER|wx.FULL_REPAINT_ON_RESIZE, + idx=self.disp_idx, gismgr=self, notebook=self.gm_cb) + + # layout for controls + cb_boxsizer = wx.BoxSizer(wx.VERTICAL) + cb_boxsizer.Add(self.curr_page.maptree, proportion=1, flag=wx.EXPAND, border=1) + self.curr_page.SetSizer(cb_boxsizer) + cb_boxsizer.Fit(self.curr_page.maptree) + self.curr_page.Layout() + self.curr_page.maptree.Layout() + + self.disp_idx += 1 + + # toolBar button handlers + def onRaster(self, event): + """Add raster menu""" + point = wx.GetMousePosition() + rastmenu = wx.Menu() + # Add items to the menu + addrast = wx.MenuItem(rastmenu, -1,'Add raster map layer') + bmp = wx.Image(os.path.join(iconpath,'image_add.png'), wx.BITMAP_TYPE_ANY) +# bmp.Rescale(16, 16) + bmp = bmp.ConvertToBitmap() + addrast.SetBitmap(bmp) + rastmenu.AppendItem(addrast) + self.Bind(wx.EVT_MENU, self.addRaster, addrast) + + addrgb = wx.MenuItem(rastmenu, -1,'Add RGB layer') + bmp = wx.Image(os.path.join(iconpath,'rgb.png'), wx.BITMAP_TYPE_ANY) +# bmp.Rescale(16, 16) + bmp = bmp.ConvertToBitmap() + addrgb.SetBitmap(bmp) + rastmenu.AppendItem(addrgb) + self.Bind(wx.EVT_MENU, self.addRGB, addrgb) + + addhis = wx.MenuItem(rastmenu, -1,'Add HIS layer') + bmp = wx.Image(os.path.join(iconpath,'his.png'), wx.BITMAP_TYPE_ANY) +# bmp.Rescale(16, 16) + bmp = bmp.ConvertToBitmap() + addhis.SetBitmap(bmp) + rastmenu.AppendItem(addhis) + self.Bind(wx.EVT_MENU, self.addHIS, addhis) + + # Popup the menu. If an item is selected then its handler + # will be called before PopupMenu returns. + self.PopupMenu(rastmenu) + rastmenu.Destroy() + + def onVector(self, event): + """Add vector menu""" + point = wx.GetMousePosition() + vectmenu = wx.Menu() + + addvect = wx.MenuItem(vectmenu, -1,'Add vector map layer') + bmp = wx.Image(os.path.join(iconpath,'map.png'), wx.BITMAP_TYPE_ANY) +# bmp.Rescale(16, 16) + bmp = bmp.ConvertToBitmap() + addvect.SetBitmap(bmp) + vectmenu.AppendItem(addvect) + self.Bind(wx.EVT_MENU, self.addVector, addvect) + + addtheme = wx.MenuItem(vectmenu, -1,'Add thematic map layer') + bmp = wx.Image(os.path.join(iconpath,'thematic.png'), wx.BITMAP_TYPE_ANY) +# bmp.Rescale(16, 16) + bmp = bmp.ConvertToBitmap() + addtheme.SetBitmap(bmp) + vectmenu.AppendItem(addtheme) + self.Bind(wx.EVT_MENU, self.addThemeMap, addtheme) + + addchart = wx.MenuItem(vectmenu, -1,'Add thematic chart layer') + bmp = wx.Image(os.path.join(iconpath,'chart_bar.png'), wx.BITMAP_TYPE_ANY) +# bmp.Rescale(16, 16) + bmp = bmp.ConvertToBitmap() + addchart.SetBitmap(bmp) + vectmenu.AppendItem(addchart) + self.Bind(wx.EVT_MENU, self.addThemeChart, addchart) + # Popup the menu. If an item is selected then its handler + # will be called before PopupMenu returns. + self.PopupMenu(vectmenu) + vectmenu.Destroy() + + def onOverlay(self, event): + """Add overlay menu""" + point = wx.GetMousePosition() + ovlmenu = wx.Menu() + + addgrid = wx.MenuItem(ovlmenu, -1,'Add grid overlay') + bmp = wx.Image(os.path.join(iconpath,'grid.png'), wx.BITMAP_TYPE_ANY) + bmp.Rescale(16, 16) + bmp = bmp.ConvertToBitmap() + addgrid.SetBitmap(bmp) + ovlmenu.AppendItem(addgrid) + self.Bind(wx.EVT_MENU, self.addGrid, addgrid) + + addlbl = wx.MenuItem(ovlmenu, -1,'Add vector labels overlay (create with v.label)') + bmp = wx.Image(os.path.join(iconpath,'tag_green.png'), wx.BITMAP_TYPE_ANY) + bmp.Rescale(16, 16) + bmp = bmp.ConvertToBitmap() + addlbl.SetBitmap(bmp) + ovlmenu.AppendItem(addlbl) + self.Bind(wx.EVT_MENU, self.addLabels, addlbl) + + # Popup the menu. If an item is selected then its handler + # will be called before PopupMenu returns. + self.PopupMenu(ovlmenu) + ovlmenu.Destroy() + + + def addRaster(self, event): + self.notebook.SetSelection(0) + self.curr_page.maptree.AddLayer('raster') + + def addRGB(self, event): + """Add RGB layer""" + self.notebook.SetSelection(0) + self.curr_page.maptree.AddLayer('rgb') + + def addHIS(self, event): + """Add HIS layer""" + self.notebook.SetSelection(0) + self.curr_page.maptree.AddLayer('his') + + def addRastLeg(self, event): + """Add raster legend""" + self.notebook.SetSelection(0) + self.curr_page.maptree.AddLayer('rastleg') + + def addVector(self, event): + """Add vector layer""" + self.notebook.SetSelection(0) + self.curr_page.maptree.AddLayer('vector') + + def addThemeMap(self, event): + """Add thematic map layer""" + self.notebook.SetSelection(0) + self.curr_page.maptree.AddLayer('thememap') + + def addThemeChart(self, event): + """Add thematic chart layer""" + self.notebook.SetSelection(0) + self.curr_page.maptree.AddLayer('themechart') + + def addCommand(self, event): + """Add command line layer""" + self.notebook.SetSelection(0) + self.curr_page.maptree.AddLayer('command') + + def addGroup(self, event): + """Add layer group""" + self.notebook.SetSelection(0) + self.curr_page.maptree.AddLayer('group') + + def addGrid(self, event): + """Add layer grid""" + self.notebook.SetSelection(0) + self.curr_page.maptree.AddLayer('grid') + + def addLabels(self, event): + """Add layer vector labels""" + self.notebook.SetSelection(0) + self.curr_page.maptree.AddLayer('labels') + + def GetSelectedDisplay(self): + return self.notebook.GetSelection() + + def deleteLayer(self, event): + """ + Delete selected map display layer in GIS Manager tree widget + """ + for layer in self.curr_page.maptree.GetSelections(): + if self.curr_page.maptree.layertype[layer] == 'group': + self.curr_page.maptree.DeleteChildren(layer) + self.curr_page.maptree.Delete(layer) + + #Misc methods + def onCloseWindow(self, event): + '''Cleanup when wxgui.py is quit''' + try: + for page in range(self.gm_cb.GetPageCount()): + self.gm_cb.GetPage(page).maptree.Map.Clean() + self.DeleteAllPages() + except: + self.DestroyChildren() + self.Destroy() + + def Nomethod(self, event): + '''Stub for testing''' + pass + event.Skip() + +class GMApp(wx.App): + """ + GMApp class + """ + def OnInit(self): +## reexec_with_pythonw() + # initialize all available image handlers + wx.InitAllImageHandlers() + # create and show main frame + mainframe = GMFrame(None, -1, "" ) + self.SetTopWindow(mainframe) + mainframe.Show() + return 1 + +def reexec_with_pythonw(): + if sys.platform == 'darwin' and\ + not sys.executable.endswith('MacOS/Python'): + print >>sys.stderr,'re-executing using pythonw' + os.execvp('pythonw',['pythonw',__file__] + sys.argv[1:]) + + +if __name__ == "__main__": + + reexec_with_pythonw() + + import gettext + gettext.install("GMApp") # replace with the appropriate catalog name + app = GMApp(0) + app.MainLoop() Property changes on: trunk/grassaddons/gui/wxgui_silk.py ___________________________________________________________________ Name: svn:executable + * From calvelo at grass.itc.it Mon Apr 16 05:14:01 2007 From: calvelo at grass.itc.it (calvelo@grass.itc.it) Date: Mon Apr 16 05:14:02 2007 Subject: [grass-addons] r506 - trunk/grassaddons/gui/gui_modules Message-ID: <200704160314.l3G3E1Xq003133@grass.itc.it> Author: calvelo Date: 2007-04-16 05:13:46 +0200 (Mon, 16 Apr 2007) New Revision: 506 Modified: trunk/grassaddons/gui/gui_modules/menuform.py Log: - cmdPanel is now totally independent. It can be used with a grassTask() structure to generate any grass-style command building interface, and it uses createCmd() and OnSetValues() as its main API Modified: trunk/grassaddons/gui/gui_modules/menuform.py =================================================================== --- trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-15 16:57:42 UTC (rev 505) +++ trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-16 03:13:46 UTC (rev 506) @@ -377,6 +377,7 @@ self.notebookpanel = cmdPanel( parent=self, task=self.task, standalone=standalone ) if standalone: self.goutput = self.notebookpanel.goutput + self.notebookpanel.OnUpdateValues = self.updateValuesHook guisizer.Add( self.notebookpanel, 1, flag = wx.EXPAND ) @@ -422,6 +423,8 @@ self.SetSizer(guisizer) self.Layout() + def updateValuesHook(self): + self.SetStatusText( self.notebookpanel.createCmd(ignoreErrors = True) ) def OnOK(self, event): cmd = self.OnApply(event) @@ -719,16 +722,15 @@ colorchooser.SetColour( new_color ) colorchooser.Refresh() p[ 'value' ] = colorchooser.GetLabel() - self.updateStatusLine() + self.OnUpdateValues() - def updateStatusLine(self): - """If we were part of a richer interface, report back the current command being built.""" - # TODO: don't tie this to a StatusLine - try: - self.GetParent().SetStatusText( self.createCmd(ignoreErrors = True) ) - except: - pass + def OnUpdateValues(self): + """If we were part of a richer interface, report back the current command being built. + This method should be set by the parent of this panel if needed. It's a hook, actually. + Beware of what is "self" in the method def, though. It will be called with no arguments.""" + pass + def OnCheckBoxMulti(self, event): """Fill the values ,-separated string according to current status of the checkboxes.""" me = event.GetId() @@ -752,7 +754,7 @@ currentValueList.append( v ) # Pack it back theParam['value'] = ','.join( currentValueList ) - self.updateStatusLine() + self.OnUpdateValues() def OnSetValue(self, event): myId = event.GetId() @@ -760,7 +762,7 @@ for porf in self.task.params + self.task.flags: if 'wxId' in porf and type( porf[ 'wxId' ] ) == type( 1 ) and porf['wxId'] == myId: porf[ 'value' ] = me.GetValue() - self.updateStatusLine() + self.OnUpdateValues() def createCmd(self, ignoreErrors = False): """Produce a command line string for feeding into GRASS. From barton at grass.itc.it Mon Apr 16 06:04:05 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Mon Apr 16 06:04:06 2007 Subject: [grass-addons] r507 - trunk/grassaddons/gui/gui_modules Message-ID: <200704160404.l3G445uO004389@grass.itc.it> Author: barton Date: 2007-04-16 06:03:57 +0200 (Mon, 16 Apr 2007) New Revision: 507 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py Log: Fix bug keeping barscale and legend from opening options dialog. Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-16 03:13:46 UTC (rev 506) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-16 04:03:57 UTC (rev 507) @@ -1635,7 +1635,7 @@ """ menuform.GUI().parseCommand(self.ovlcmd, gmpath, - completed=(self.parent.getOptData,self.ovltype,self.params), + completed=(self.Parent.getOptData,self.ovltype,self.params), parentframe=None) class TextDialog(wx.Dialog): From calvelo at grass.itc.it Mon Apr 16 06:55:51 2007 From: calvelo at grass.itc.it (calvelo@grass.itc.it) Date: Mon Apr 16 06:55:52 2007 Subject: [grass-addons] r508 - trunk/grassaddons/gui/gui_modules Message-ID: <200704160455.l3G4tpMC005030@grass.itc.it> Author: calvelo Date: 2007-04-16 06:55:38 +0200 (Mon, 16 Apr 2007) New Revision: 508 Modified: trunk/grassaddons/gui/gui_modules/grass-interface.dtd trunk/grassaddons/gui/gui_modules/menuform.py Log: - value sets may have names and descriptions; slurp names only, so that selectors are not confused. TODO: record descriptions also, so that they can be used for tooltips or other help. Modified: trunk/grassaddons/gui/gui_modules/grass-interface.dtd =================================================================== --- trunk/grassaddons/gui/gui_modules/grass-interface.dtd 2007-04-16 04:03:57 UTC (rev 507) +++ trunk/grassaddons/gui/gui_modules/grass-interface.dtd 2007-04-16 04:55:38 UTC (rev 508) @@ -115,18 +115,18 @@ --> - - + Modified: trunk/grassaddons/gui/gui_modules/menuform.py =================================================================== --- trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-16 04:03:57 UTC (rev 507) +++ trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-16 04:55:38 UTC (rev 508) @@ -244,7 +244,7 @@ self.description = self.description + ch if self.inDefaultContent: self.param_default = self.param_default + ch - if self.inValueContent: + if self.inValueContent and not self.inDescriptionContent: self.value_tmp = self.value_tmp + ch if self.inGuisection: self.param_guisection = self.param_guisection + ch From calvelo at grass.itc.it Mon Apr 16 08:00:16 2007 From: calvelo at grass.itc.it (calvelo@grass.itc.it) Date: Mon Apr 16 08:00:16 2007 Subject: [grass-addons] r509 - trunk/grassaddons/gui/gui_modules Message-ID: <200704160600.l3G60Gnv006005@grass.itc.it> Author: calvelo Date: 2007-04-16 08:00:03 +0200 (Mon, 16 Apr 2007) New Revision: 509 Modified: trunk/grassaddons/gui/gui_modules/menuform.py Log: - better test task: use with 'test' as the grass command - now cmdPanel may be queried by (i) buildCmd, which returns an array of arguments and (ii) createCmd, which is the usual cmdline string. This allows for both system-style use (with the usual quote madness), and a saner execve-style command launch. Modified: trunk/grassaddons/gui/gui_modules/menuform.py =================================================================== --- trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-16 04:55:38 UTC (rev 508) +++ trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-16 06:00:03 UTC (rev 509) @@ -31,8 +31,9 @@ # # - verify option value types # - add tooltips +# - use DOM instead of SAX !!! """ -__version__ ="$Date: 2006/08/06 21:21:01 $" +__version__ ="$Revision $" import wx import sys @@ -135,7 +136,7 @@ def text_beautify( someString ): "Make really long texts shorter" # TODO: remove magic number (calculate a correct value from - # pixelSize of text and the magic number for maximum size + # pixelSize of text and the magic number for maximum size) return escape_ampersand( os.linesep.join( textwrap.wrap( normalize_whitespace(someString), 70 ) ) ) def escape_ampersand(text): @@ -245,6 +246,19 @@ if self.inDefaultContent: self.param_default = self.param_default + ch if self.inValueContent and not self.inDescriptionContent: + # Beware: value_tmp will get anything outside of a + # so in this snippet: + # + # + # a + # a desc + # + # + # 'a desc' will not be recorded anwhere; this unburdens further + # handling of value sets to distinguish between those that do define + # descriptions and those that do not. + # + # TODO: a set of flags to treat this case of a description sub-element self.value_tmp = self.value_tmp + ch if self.inGuisection: self.param_guisection = self.param_guisection + ch @@ -764,33 +778,42 @@ porf[ 'value' ] = me.GetValue() self.OnUpdateValues() - def createCmd(self, ignoreErrors = False): - """Produce a command line string for feeding into GRASS. + def buildCmd(self, ignoreErrors = False): + """Produce an array of command ame and arguments for feeding + into some execve-like command processor. If ignoreErrors==True then it will return whatever has been built so far, even though it would not be a correct command for GRASS.""" - cmd = self.task.name + cmd = [self.task.name] errors = 0 errStr = "" dcmd_params = {} for flag in self.task.flags: if 'value' in flag and flag['value']: - cmd += ' -' + flag['name'] + cmd += [ '-' + flag['name'] ] for p in self.task.params: if p.get('value','') == '' and p.get('required','no') != 'no': - cmd += ' ' + p['name'] + '=' + _('') + cmd += [ p['name'] + '=' + _('')] errStr += _("Parameter %s (%s) is missing\n") % ( p['name'], p['description'] ) errors += 1 if p.get('value','') != '' and p['value'] != p.get('default','') : - cmd += ' ' + p['name'] + '=' + p['value'] + cmd += [ p['name'] + '=' + p['value'] ] if errors and not ignoreErrors: self.OnError(errStr) return None return cmd + def createCmd( self, ignoreErrors = False ): + """Produce a command line string for feeding into GRASS. + + If ignoreErrors==True then it will return whatever has been + built so far, even though it would not be a correct command + for GRASS.""" + return ' '.join( self.buildCmd( ignoreErrors=ignoreErrors ) ) + def OnError(self, errMsg): dlg = wx.MessageDialog(self, errMsg, _("Error"), wx.OK | wx.ICON_ERROR) dlg.ShowModal() @@ -905,6 +928,7 @@ "name" : "transparent_color", "description" : "This color becomes transparent when set to none", "guisection" : "tab", + "gisprompt" : True, "prompt" : "color" },{ "name" : "multi", @@ -926,16 +950,17 @@ task.flags = [ { "name" : "a", - "description" : "Some flag", + "description" : "Some flag, will appear in Main since it is required", "required" : "yes" },{ "name" : "b", - "description" : "pre-filled flag", + "description" : "pre-filled flag, will appear in options since it is not required", "value" : True },{ - "name" : "h", - "description" : "hidden flag", - "hidden" : "yes" + "name" : "hidden_flag", + "description" : "hidden flag, should not be changeable", + "hidden" : "yes", + "value" : True } ] GrassGUIApp( task ).MainLoop() From barton at grass.itc.it Mon Apr 16 08:50:36 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Mon Apr 16 08:50:37 2007 Subject: [grass-addons] r510 - trunk/grassaddons/gui/gui_modules Message-ID: <200704160650.l3G6oahG006153@grass.itc.it> Author: barton Date: 2007-04-16 08:50:27 +0200 (Mon, 16 Apr 2007) New Revision: 510 Modified: trunk/grassaddons/gui/gui_modules/toolbars.py Log: Start of a force render option. Modified: trunk/grassaddons/gui/gui_modules/toolbars.py =================================================================== --- trunk/grassaddons/gui/gui_modules/toolbars.py 2007-04-16 06:00:03 UTC (rev 509) +++ trunk/grassaddons/gui/gui_modules/toolbars.py 2007-04-16 06:50:27 UTC (rev 510) @@ -50,6 +50,13 @@ type=wx.BITMAP_TYPE_ANY), bmpDisabled=wx.NullBitmap, kind=wx.ITEM_NORMAL, shortHelp="Display map", longHelp="") + + self.rendermap = self.toolbar.AddLabelTool(id=wx.ID_ANY, label="rendermap", + bitmap=wx.Bitmap(name=os.path.join(icons,"gui-redraw.gif"), + type=wx.BITMAP_TYPE_ANY), + bmpDisabled=wx.NullBitmap, kind=wx.ITEM_NORMAL, + shortHelp="Re-render map", longHelp="Force re-rendering of all layers") + self.erase = self.toolbar.AddLabelTool(wx.ID_ANY, "erase", wx.Bitmap(os.path.join(icons,"gui-erase.gif"), wx.BITMAP_TYPE_ANY), @@ -147,6 +154,7 @@ self.toolbar.Realize() self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.ReDraw, self.displaymap) + self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.ReRender, self.rendermap) self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.Pointer, self.pointer) self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnZoomIn, self.zoomin) self.mapdisplay.Bind(wx.EVT_TOOL, self.mapdisplay.OnZoomOut, self.zoomout) From barton at grass.itc.it Mon Apr 16 08:51:40 2007 From: barton at grass.itc.it (barton@grass.itc.it) Date: Mon Apr 16 08:51:41 2007 Subject: [grass-addons] r511 - trunk/grassaddons/gui/gui_modules Message-ID: <200704160651.l3G6peRn006173@grass.itc.it> Author: barton Date: 2007-04-16 08:51:31 +0200 (Mon, 16 Apr 2007) New Revision: 511 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py Log: Start of a force render option. Improved save display to file. Only overlay text not saving now. Why not??? Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-16 06:50:27 UTC (rev 510) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-16 06:51:31 UTC (rev 511) @@ -347,8 +347,10 @@ """ All that is needed here is to draw the buffer to screen """ - dc = wx.BufferedPaintDC(self) + self._Buffer = wx.EmptyBitmap(self.Map.width, self.Map.height) + dc = wx.BufferedPaintDC(self, self._Buffer) + # use PrepateDC to set position correctly self.PrepareDC(dc) # we need to clear the dc BEFORE calling PrepareDC @@ -683,7 +685,6 @@ # dragging or drawing box with left button if self.mouse['box'] == 'pan': self.DragMap(end) - self.DragItem(99, event) # dragging decoration overlay item elif self.mouse['box'] == 'point' and self.dragid != None and self.dragid != 99: @@ -1140,6 +1141,13 @@ """ self.MapWindow.UpdateMap() + def ReRender(self, event): + """ + Rerender button clicked + """ + self.render = True + self.MapWindow.UpdateMap() + def Pointer(self, event): """Pointer button clicled""" self.MapWindow.mouse['box'] = "point" @@ -1217,13 +1225,29 @@ """ Save to file """ + filetype = "PNG file (*.png)|*.png|"\ + "TIF file (*.tif)|*.tif|"\ + "GIF file (*.gif)|*.gif" + dlg = wx.FileDialog(self, "Choose a file name to save the image as a PNG to", defaultDir = "", defaultFile = "", - wildcard = "*.png", - style=wx.SAVE) + wildcard = filetype, + style=wx.SAVE|wx.FD_OVERWRITE_PROMPT) if dlg.ShowModal() == wx.ID_OK: - self.MapWindow.SaveToFile(dlg.GetPath(), wx.BITMAP_TYPE_PNG) + base = os.path.splitext(dlg.GetPath())[0] + ext = os.path.splitext(dlg.GetPath())[1] + if dlg.GetFilterIndex() == 0: + type = wx.BITMAP_TYPE_PNG + path = dlg.GetPath() + if ext != '.png': path = base+'.png' + elif dlg.GetFilterIndex() == 1: + type = wx.BITMAP_TYPE_TIF + if ext != '.tif': path = base+'.tif' + elif dlg.GetFilterIndex() == 2: + type = wx.BITMAP_TYPE_TIF + if ext != '.gif': path = base+'.gif' + self.MapWindow.SaveToFile(path, type) dlg.Destroy() def PrintMap(self, event): From landa at grass.itc.it Mon Apr 16 15:43:46 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Mon Apr 16 15:43:46 2007 Subject: [grass-addons] r512 - trunk/grassaddons/gui/gui_modules Message-ID: <200704161343.l3GDhkUr010945@grass.itc.it> Author: landa Date: 2007-04-16 15:43:45 +0200 (Mon, 16 Apr 2007) New Revision: 512 Added: trunk/grassaddons/gui/gui_modules/debug.py Log: debug module added Added: trunk/grassaddons/gui/gui_modules/debug.py =================================================================== --- trunk/grassaddons/gui/gui_modules/debug.py (rev 0) +++ trunk/grassaddons/gui/gui_modules/debug.py 2007-04-16 13:43:45 UTC (rev 512) @@ -0,0 +1,64 @@ +""" +MODULE: debug + +CLASSES: + * Debug + +PURPOSE: GRASS debugging + +AUTHORS: The GRASS Development Team + Martin Landa + +COPYRIGHT: (C) 2007 by the GRASS Development Team + This program is free software under the GNU General Public + License (>=v2). Read the file COPYING that comes with GRASS + for details. +""" + +import grassenv + +class Debug: + """ + GRASS Debugging + + Usage: + import cmd + + cmd.Command (cmd="g.gisenv set=DEBUG=3") + + import grassenv # or reload (grassenv) + + debug = Debug() + debug.msg (3, "message level=%d" % 3) + """ + def __init__(self): + self.debuglevel = 0 + self._update_level() + + def _update_level(self): + if grassenv.env.has_key ("DEBUG"): + level = int (grassenv.env["DEBUG"]) + if self.debuglevel != level: + self.debuglevel = level + + def msg (self, level, message): + self._update_level() + if self.debuglevel > 0 and level >= self.debuglevel: + print "D%d/%d: %s" % (level, level, message) + + +# testing +if __name__ == "__main__": + import cmd + cmd.Command (cmd="g.gisenv set=DEBUG=3") + + for unit in range (2): + if unit == 1: + cmd.Command (cmd="g.gisenv set=DEBUG=0") + + reload (grassenv) # reload GRASS environments ! + debug = Debug() + + print "DEBUG=%d" % debug.debuglevel + for level in range (4): + debug.msg (level, "message level=%d" % level) From neteler at grass.itc.it Mon Apr 16 16:41:50 2007 From: neteler at grass.itc.it (neteler@grass.itc.it) Date: Mon Apr 16 16:41:52 2007 Subject: [grass-addons] r513 - trunk/grassaddons/grassflyer/flyer1/en Message-ID: <200704161441.l3GEfok7011513@grass.itc.it> Author: neteler Date: 2007-04-16 16:41:50 +0200 (Mon, 16 Apr 2007) New Revision: 513 Added: trunk/grassaddons/grassflyer/flyer1/en/caption.sty Log: added (rather) new caption style which isn't available on older teTeXs Added: trunk/grassaddons/grassflyer/flyer1/en/caption.sty =================================================================== --- trunk/grassaddons/grassflyer/flyer1/en/caption.sty (rev 0) +++ trunk/grassaddons/grassflyer/flyer1/en/caption.sty 2007-04-16 14:41:50 UTC (rev 513) @@ -0,0 +1,688 @@ +%% +%% This is file `caption.sty', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% caption.dtx (with options: `package') +%% +%% Copyright (C) 1994-2004 Axel Sommerfeldt (caption@sommerfeldt.net) +%% +%% -------------------------------------------------------------------------- +%% +%% This work may be distributed and/or modified under the +%% conditions of the LaTeX Project Public License, either version 1.3 +%% of this license or (at your option) any later version. +%% The latest version of this license is in +%% http://www.latex-project.org/lppl.txt +%% and version 1.3 or later is part of all distributions of LaTeX +%% version 2003/12/01 or later. +%% +%% This work has the LPPL maintenance status "maintained". +%% +%% This Current Maintainer of this work is Axel Sommerfeldt. +%% +%% This work consists of the files caption.ins, caption.dtx, +%% caption2.dtx, caption.xml, and anleitung.tex and the derived files +%% caption.sty, caption2.sty, and manual.tex. +%% +\NeedsTeXFormat{LaTeX2e}[1994/12/01] +\ProvidesPackage{caption}[2004/07/16 v3.0c Customising captions (AS)] +\providecommand*\@nameundef[1]{% + \expandafter\let\csname #1\endcsname\@undefined} +\providecommand\l@addto@macro[2]{% + \begingroup + \toks@\expandafter{#1#2}% + \edef\@tempa{\endgroup\def\noexpand#1{\the\toks@}}% + \@tempa} +\def\bothIfFirst#1#2{% + \protected@edef\caption@tempa{#1}% + \ifx\caption@tempa\@empty\else + #1#2% + \fi} +\def\bothIfSecond#1#2{% + \protected@edef\caption@tempa{#2}% + \ifx\caption@tempa\@empty\else + #1#2% + \fi} +\def\caption@ifinlist#1#2{% + \let\next\@secondoftwo + \edef\caption@tempa{#1}% + \@for\caption@tempb:={#2}\do{% + \ifx\caption@tempa\caption@tempb + \let\next\@firstoftwo + \fi}% + \next} +\def\caption@setbool#1#2{% + \caption@ifinlist{#2}{1,true,yes,on}{% + \expandafter\let\csname caption@if#1\endcsname\@firstoftwo + }{\caption@ifinlist{#2}{0,false,no,off}{% + \expandafter\let\csname caption@if#1\endcsname\@secondoftwo + }{% + \PackageError{caption}{Undefined boolean value `#2'}{\caption@eh}% + }}} +\def\caption@ifbool#1{\@nameuse{caption@if#1}} +\providecommand\captionsize{}% changed v3.0a+c +\newdimen\captionmargin +\newdimen\captionwidth +\newif\ifcaption@width +\newcommand\caption@setmargin{% + \caption@widthfalse + \setlength\captionmargin} +\newcommand\caption@setwidth{% + \caption@widthtrue + \setlength\captionwidth} +\newdimen\captionindent +\newdimen\captionparindent +\newdimen\captionhangindent +\newif\ifcaption@star +\@ifundefined{abovecaptionskip}{% + \newlength\abovecaptionskip\setlength\abovecaptionskip{10\p@}}{} +\@ifundefined{belowcaptionskip}{% + \newlength\belowcaptionskip\setlength\belowcaptionskip{0\p@}}{} +\newcommand\caption@eh{% + If you do not understand this error, please take a closer look\MessageBreak + at the documentation of the `caption' package.\MessageBreak + \@ehc} +\RequirePackage{keyval}[1997/11/10] +\providecommand*\undefine@key[2]{% + \@nameundef{KV@#1@#2}\@nameundef{KV@#1@#2@default}} +\newcommand\caption@setdefault{\captionsetup{% + format=default,labelformat=default,labelsep=default,justification=default,% + font=default,labelfont=default,textfont=default,% + margin=0pt,indention=0pt,parindent=0pt,hangindent=0pt,singlelinecheck}} +\newcommand*\DeclareCaptionStyle[1]{% + \@ifnextchar[{\caption@declarestyle{#1}}{\caption@declarestyle{#1}[]}} +\def\caption@declarestyle#1[#2]#3{% bugfixed v3.0a + \global\@namedef{caption@sls@#1}{#2}% + \global\@namedef{caption@sty@#1}{#3}} +\@onlypreamble\DeclareCaptionStyle +\@onlypreamble\caption@declarestyle +\newcommand*\caption@setstyle[1]{% + \@ifundefined{caption@sty@#1}% + {\PackageError{caption}{Undefined caption style `#1'}{\caption@eh}}% + {\expandafter\let\expandafter\caption@sls\csname caption@sls@#1\endcsname + \caption@setdefault\caption@esetup{\csname caption@sty@#1\endcsname}}} +\DeclareCaptionStyle{default}[justification=centering]{} +\newcommand\DeclareCaptionFormat[2]{% bugfixed v3.0a + \global\long\expandafter\def\csname caption@fmt@#1\endcsname##1##2##3{#2}} +\@onlypreamble\DeclareCaptionFormat +\newcommand*\caption@setformat[1]{% + \@ifundefined{caption@fmt@#1}% + {\PackageError{caption}{Undefined caption format `#1'}{\caption@eh}}% + {\expandafter\let\expandafter\caption@fmt\csname caption@fmt@#1\endcsname}} +\DeclareCaptionFormat{normal}{#1#2#3\par} +\DeclareCaptionFormat{hang}{% + \@hangfrom{#1#2}% + \advance\captionparindent\hangindent + \advance\captionhangindent\hangindent + \caption@@par + #3\par} +\def\caption@fmt@default{\caption@fmt@normal} +\newcommand*\DeclareCaptionLabelFormat[2]{% bugfixed v3.0a + \global\expandafter\def\csname caption@lfmt@#1\endcsname##1##2{#2}} +\@onlypreamble\DeclareCaptionLabelFormat +\newcommand*\caption@setlabelformat[1]{% + \@ifundefined{caption@lfmt@#1}% + {\PackageError{caption}{Undefined caption label format `#1'}{\caption@eh}}% + {\expandafter\let\expandafter\caption@lfmt\csname caption@lfmt@#1\endcsname}} +\DeclareCaptionLabelFormat{empty}{} +\DeclareCaptionLabelFormat{simple}{\bothIfFirst{#1}{\nobreakspace}#2} +\DeclareCaptionLabelFormat{parens}{\bothIfFirst{#1}{\nobreakspace}(#2)} +\def\caption@lfmt@default{\caption@lfmt@simple} +\newcommand\DeclareCaptionLabelSeparator[2]{% bugfixed v3.0a + \global\long\@namedef{caption@lsep@#1}{#2}} +\@onlypreamble\DeclareCaptionLabelSeparator +\newcommand*\caption@setlabelseparator[1]{% + \@ifundefined{caption@lsep@#1}% + {\PackageError{caption}{Undefined caption label separator `#1'}{\caption@eh}}% + {\expandafter\let\expandafter\caption@lsep\csname caption@lsep@#1\endcsname}} +\DeclareCaptionLabelSeparator{none}{} +\DeclareCaptionLabelSeparator{colon}{: } +\DeclareCaptionLabelSeparator{period}{. } +\DeclareCaptionLabelSeparator{space}{ } +\DeclareCaptionLabelSeparator{quad}{\quad} +\DeclareCaptionLabelSeparator{newline}{\newline} +\DeclareCaptionLabelSeparator{widespace}{\hspace{1em plus .3em}}% obsolete, do not use! +\def\caption@lsep@default{\caption@lsep@colon} +\newcommand*\DeclareCaptionJustification[2]{% bugfixed v3.0a + \global\@namedef{caption@hj@#1}{#2}} +\@onlypreamble\DeclareCaptionJustification +\newcommand*\caption@setjustification[1]{% + \@ifundefined{caption@hj@#1}% + {\PackageError{caption}{Undefined caption justification `#1'}{\caption@eh}}% + {\expandafter\let\expandafter\caption@hj\csname caption@hj@#1\endcsname}} +\newcommand\caption@centerfirst{% + \edef\caption@normaladjust{% + \leftskip\the\leftskip + \rightskip\the\rightskip + \parfillskip\the\parfillskip\relax}% + \leftskip\z@\@plus -1fil% + \rightskip\z@\@plus 1fil% + \parfillskip\z@skip + \noindent\hskip\z@\@plus 2fil% + \@setpar{\@@par\@restorepar\caption@normaladjust}} +\newcommand\caption@centerlast{% + \leftskip\z@\@plus 1fil% + \rightskip\z@\@plus -1fil% + \parfillskip\z@\@plus 2fil\relax} +\DeclareCaptionJustification{justified}{} +\DeclareCaptionJustification{centering}{\centering} +\DeclareCaptionJustification{centerfirst}{\caption@centerfirst} +\DeclareCaptionJustification{centerlast}{\caption@centerlast} +\DeclareCaptionJustification{raggedleft}{\raggedleft} +\DeclareCaptionJustification{raggedright}{\raggedright} +\def\caption@hj@default{\caption@hj@justified} +\DeclareCaptionJustification{Centering}{% + \caption@ragged\Centering\centering} +\DeclareCaptionJustification{RaggedLeft}{% + \caption@ragged\RaggedLeft\raggedleft} +\DeclareCaptionJustification{RaggedRight}{% + \caption@ragged\RaggedRight\raggedright} +\newcommand*\caption@ragged[2]{% + \@ifundefined{caption\string#1}{% + \PackageWarning{caption}{% + Cannot locate the `ragged2e' package, therefore\MessageBreak + substituting \string#2 for \string#1\MessageBreak}% + \global\@namedef{caption\string#1}}{}% + #2} +\AtBeginDocument{\IfFileExists{ragged2e.sty}{% + \RequirePackage{ragged2e}\let\caption@ragged\@firstoftwo}{}} +\newcommand\DeclareCaptionFont[2]{% bugfixed v3.0a + \define@key{caption@fnt}{#1}[]{\g@addto@macro\caption@tempa{#2}}} +\@onlypreamble\DeclareCaptionFont +\newcommand*\caption@setfont[2]{% + \let\caption@tempa\@empty + \begingroup + \setkeys{caption@fnt}{#2}% + \endgroup + \expandafter\let\csname caption#1\endcsname\caption@tempa} +\DeclareCaptionFont{default}{} +\DeclareCaptionFont{scriptsize}{\scriptsize} +\DeclareCaptionFont{footnotesize}{\footnotesize} +\DeclareCaptionFont{small}{\small} +\DeclareCaptionFont{normalsize}{\normalsize} +\DeclareCaptionFont{large}{\large} +\DeclareCaptionFont{Large}{\Large} +\DeclareCaptionFont{up}{\upshape} +\DeclareCaptionFont{it}{\itshape} +\DeclareCaptionFont{sl}{\slshape} +\DeclareCaptionFont{sc}{\scshape} +\DeclareCaptionFont{md}{\mdseries} +\DeclareCaptionFont{bf}{\bfseries} +\DeclareCaptionFont{rm}{\rmfamily} +\DeclareCaptionFont{sf}{\sffamily} +\DeclareCaptionFont{tt}{\ttfamily} +\newcommand*\caption@setposition[1]{% improved v3.0a + \caption@ifinlist{#1}{t,top,above}{% + \let\caption@position\@firstoftwo + }{\caption@ifinlist{#1}{b,bottom,below,default}{% + \let\caption@position\@secondoftwo + }{\caption@ifinlist{#1}{a,auto}{% + \let\caption@position\@undefined + }{% + \PackageError{caption}{Undefined caption position `#1'}{\caption@eh}% + }}}} +\def\captionsetup{\@ifnextchar[\caption@setuptype\caption@setup} +\def\caption@setuptype[#1]#2{% bugfixed v3.0a + \@ifundefined{caption@typ@#1}% + {\@namedef{caption@typ@#1}{#2}}% + {\expandafter\l@addto@macro\csname caption@typ@#1\endcsname{,#2}}} +\def\caption@setup{\setkeys{caption}} +\def\caption@esetup#1{% + \edef\caption@tempa{\noexpand\caption@setup{#1}}% + \caption@tempa} +\def\caption@settype#1{% + \@ifundefined{caption@typ@#1}{}{% + \caption@esetup{\csname caption@typ@#1\endcsname}}}% +\let\caption@setfloattype\caption@settype% new v3.0a +\newcommand*\clearcaptionsetup[1]{\@nameundef{caption@typ@#1}} +\newcommand*\showcaptionsetup[2][]{% + \def\caption@tempa{#1}% + \ifx\caption@tempa\@empty + \def\caption@tempa{Caption\space}% + \else + \def\caption@tempa{#1 Caption\space}% + \fi + \GenericWarning{\caption@tempa}{% + \caption@tempa Info: KV list on `#2'\MessageBreak + Data: (% + \@ifundefined{caption@typ@#2}{% + % Empty -- print nothing. + }{% + \@nameuse{caption@typ@#2}% + }% + )}} +\newcommand\caption@beginhook{} +\newcommand\caption@endhook{} +\newcommand\AtBeginCaption{\l@addto@macro\caption@beginhook} +\newcommand\AtEndCaption{\l@addto@macro\caption@endhook} +\newcommand\DeclareCaptionOption{% + \@ifstar{\caption@declareoption\AtEndOfPackage}{\caption@declareoption\@gobble}} +\newcommand*\caption@declareoption[2]{% + #1{\undefine@key{caption}{#2}}\define@key{caption}{#2}} +\@onlypreamble\DeclareCaptionOption +\@onlypreamble\caption@declareoption +\DeclareCaptionOption{default}[]{% + \caption@setup{style=default,position=default,aboveskip=10pt,belowskip=0pt}} +\DeclareCaptionOption{style}{\caption@setstyle{#1}} +\DeclareCaptionOption{format}{\caption@setformat{#1}} +\DeclareCaptionOption{labelformat}{\caption@setlabelformat{#1}} +\DeclareCaptionOption{labelsep}{\caption@setlabelseparator{#1}} +\DeclareCaptionOption{labelseparator}{\caption@setlabelseparator{#1}} +\DeclareCaptionOption{justification}{\caption@setjustification{#1}} +\DeclareCaptionOption{size}{\caption@setfont{size}{#1}}% changed v3.0a +\DeclareCaptionOption{font}{\caption@setfont{font}{#1}} +\DeclareCaptionOption{labelfont}{\caption@setfont{labelfont}{#1}} +\DeclareCaptionOption{textfont}{\caption@setfont{textfont}{#1}} +\DeclareCaptionOption{margin}{\caption@setmargin{#1}} +\DeclareCaptionOption{width}{\caption@setwidth{#1}} +\DeclareCaptionOption{indent}[\leftmargini]{\setlength\captionindent{#1}} +\DeclareCaptionOption{indention}[\leftmargini]{\setlength\captionindent{#1}} +\DeclareCaptionOption{parindent}[\parindent]{\setlength\captionparindent{#1}}% changed v3.0b +\DeclareCaptionOption{hangindent}[0pt]{\setlength\captionhangindent{#1}}% changed v3.0b +\DeclareCaptionOption{parskip}[5pt]{\AtBeginCaption{\setlength\parskip{#1}}} +\DeclareCaptionOption{singlelinecheck}[1]{\caption@setbool{slc}{#1}} +\DeclareCaptionOption{aboveskip}{\setlength\abovecaptionskip{#1}} +\DeclareCaptionOption{belowskip}{\setlength\belowcaptionskip{#1}} +\DeclareCaptionOption{position}{\caption@setposition{#1}} +\DeclareCaptionOption{listof}{\caption@setbool{lof}{#1}}% new v3.0b +\DeclareCaptionOption{debug}{\def\caption@debug{#1}} +\captionsetup{style=default,position=default,listof=1,debug=0} +\newcommand\caption@fixposition{% + \ifx\caption@position\@undefined + \caption@autoposition + \fi} +\newcommand\caption@autoposition{% bugfixed v3.0a + \ifvmode + \ifodd\caption@debug\relax + \edef\caption@tempa{\the\prevdepth}% + \PackageInfo{caption}{\protect\prevdepth=\caption@tempa}% + \fi + \ifdim\prevdepth>-\p@ + \let\caption@position\@secondoftwo + \else + \let\caption@position\@firstoftwo + \fi + \else + \ifodd\caption@debug\relax + \PackageInfo{caption}{no \protect\prevdepth}% + \fi + \let\caption@position\@secondoftwo + \fi} +\newcommand\caption@iftop{% bugfixed v3.0a + \ifx\caption@position\@firstoftwo + \expandafter\@firstoftwo + \else + \expandafter\@secondoftwo + \fi} +\newcommand\caption@make[2]{% + \caption@@make{\caption@lfmt{#1}{#2}}} +\newcommand\caption@@make[2]{% + \caption@beginhook + \caption@calcmargin + \advance\captionmargin by \captionindent + \advance\captionwidth by -\captionindent + \hskip\captionmargin + \vbox{\hsize=\captionwidth + \ifdim\captionindent=\z@\else + \hskip-\captionindent + \fi + \caption@ifslc{% + \ifx\caption@sls\@empty\else + \caption@beginslc + \sbox\@tempboxa{\caption@@@make{#1}{#2}}% + \ifdim\wd\@tempboxa >\hsize + \caption@endslc + \else + \caption@endslc + \caption@esetup\caption@sls + \fi + \fi}{}% + \captionsize\captionfont\strut + \caption@@@make{#1}{#2}}% + \caption@endhook + \global\caption@starfalse} +\newcommand\caption@calcmargin{% + \ifcaption@width + \captionmargin\hsize + \advance\captionmargin by -\captionwidth + \divide\captionmargin by 2 + \else + \captionwidth\hsize + \advance\captionwidth by -2\captionmargin + \fi + \ifodd\caption@debug\relax + \PackageInfo{caption}{\protect\hsize=\the\hsize, + \protect\margin=\the\captionmargin, + \protect\width=\the\captionwidth}% + \fi} +\newcommand\caption@beginslc{% + \begingroup + \let\label\@gobble\let\@footnotetext\@gobble + \def\stepcounter##1{\advance\csname c@##1\endcsname\@ne\relax}} +\newcommand\caption@endslc{% + \endgroup} +\newcommand\caption@@@make[2]{% + \ifcaption@star + \let\caption@lfmt\@gobbletwo + \let\caption@lsep\relax + \fi + \def\caption@tempa{#2}% + \def\caption@tempb{\ignorespaces}% + \ifx\caption@tempa\caption@tempb + \let\caption@tempa\@empty + \fi + \ifx\caption@tempa\@empty + \let\caption@lsep\relax + \fi + \def\caption@@par{% + \parindent\captionparindent\hangindent\captionhangindent}% + \@setpar{\@@par\caption@@par}\caption@@par + \caption@hj\captionsize\captionfont + \caption@fmt{{\captionlabelfont#1}}% + {{\captionlabelfont\caption@lsep}}% + {{\captiontextfont\nobreak\hskip\z@skip#2\par}}} +\DeclareCaptionOption{config}[caption]{% + \InputIfFileExists{#1.cfg}{\typeout{*** Local configuration file + #1.cfg used ***}}% + {\PackageWarning{caption}{Configuration + file #1.cfg not found}}} +\DeclareCaptionOption*{figureposition}{\captionsetup[figure]{position=#1}}% new v3.0a +\DeclareCaptionOption*{tableposition}{\captionsetup[table]{position=#1}}% new v3.0a +\DeclareCaptionOption*{normal}[]{\caption@setformat{normal}} +\DeclareCaptionOption*{isu}[]{\caption@setformat{hang}} +\DeclareCaptionOption*{hang}[]{\caption@setformat{hang}} +\DeclareCaptionOption*{center}[]{\caption@setjustification{centering}} +\DeclareCaptionOption*{anne}[]{\caption@setjustification{centerlast}} +\DeclareCaptionOption*{centerlast}[]{\caption@setjustification{centerlast}} +\DeclareCaptionOption*{nooneline}[]{\caption@setbool{slc}{0}} +\DeclareCaptionOption*{scriptsize}[]{\def\captionfont{\scriptsize}} +\DeclareCaptionOption*{footnotesize}[]{\def\captionfont{\footnotesize}} +\DeclareCaptionOption*{small}[]{\def\captionfont{\small}} +\DeclareCaptionOption*{normalsize}[]{\def\captionfont{\normalsize}} +\DeclareCaptionOption*{large}[]{\def\captionfont{\large}} +\DeclareCaptionOption*{Large}[]{\def\captionfont{\Large}} +\DeclareCaptionOption*{up}[]{\l@addto@macro\captionlabelfont\upshape} +\DeclareCaptionOption*{it}[]{\l@addto@macro\captionlabelfont\itshape} +\DeclareCaptionOption*{sl}[]{\l@addto@macro\captionlabelfont\slshape} +\DeclareCaptionOption*{sc}[]{\l@addto@macro\captionlabelfont\scshape} +\DeclareCaptionOption*{md}[]{\l@addto@macro\captionlabelfont\mdseries} +\DeclareCaptionOption*{bf}[]{\l@addto@macro\captionlabelfont\bfseries} +\DeclareCaptionOption*{rm}[]{\l@addto@macro\captionlabelfont\rmfamily} +\DeclareCaptionOption*{sf}[]{\l@addto@macro\captionlabelfont\sffamily} +\DeclareCaptionOption*{tt}[]{\l@addto@macro\captionlabelfont\ttfamily} +\caption@setbool{ruled}{0} +\DeclareCaptionOption*{ruled}[]{\caption@setbool{ruled}{1}} +\newcommand*\DeclareCaptionPackage[1]{% + \caption@setbool{pkt@#1}{1}% + \DeclareCaptionOption*{#1}{\caption@setbool{pkt@#1}{##1}}} +\DeclareCaptionPackage{caption} +\DeclareCaptionPackage{float} +\DeclareCaptionPackage{listings} +\DeclareCaptionPackage{longtable} +\DeclareCaptionPackage{rotating} +\DeclareCaptionPackage{sidecap} +\DeclareCaptionPackage{supertabular} +\let\DeclareCaptionPackage\@undefined +\def\ProcessOptionsWithKV#1{% bugfixed v3.0a + \let\@tempc\relax + \let\caption@tempa\@empty + \@for\CurrentOption:=\@classoptionslist\do{% + \@ifundefined{KV@#1@\CurrentOption}% + {}% + {% + \edef\caption@tempa{\caption@tempa,\CurrentOption,}% + \@expandtwoargs\@removeelement\CurrentOption + \@unusedoptionlist\@unusedoptionlist + }% + }% + \edef\caption@tempa{% + \noexpand\setkeys{#1}{% + \caption@tempa\@ptionlist{\@currname.\@currext}% + }% + }% + \caption@tempa + \let\CurrentOption\@empty + \AtEndOfPackage{\let\@unprocessedoptions\relax}} +\ProcessOptionsWithKV{caption} +\let\ProcessOptionsWithKV\@undefined +\def\captionof{\@ifstar{\caption@of{\caption*}}{\caption@of\caption}} +\newcommand*\caption@of[2]{\def\@captype{#2}#1} +\providecommand\ContinuedFloat{% + \ifx\@captype\@undefined + \@latex@error{\noexpand\ContinuedFloat outside float}\@ehd + \else + \addtocounter{\@captype}{\m@ne}% + \fi}% +\newcommand*\caption@floatname[1]{\@nameuse{#1name}} +\newcommand*\caption@thefloat[1]{\@nameuse{the#1}} +\def\caption@letfloattype#1{% + \def\caption@setfloattype##1{% + \caption@settype{##1}\caption@settype{#1}}} +\newcommand*\caption@begin[1]{% + \begingroup + \caption@setfloattype{#1}% + \@namedef{fnum@#1}{% + \caption@lfmt{\caption@floatname{#1}}{\caption@thefloat{#1}}}% + \caption@fixposition + \global\let\caption@fixedposition\caption@position + \caption@@begin{#1}} +\newcommand*\caption@beginex[1]{% + \caption@begin{#1}% + \caption@preparelof} +\newcommand*\caption@end{% + \caption@@end + \endgroup + \let\caption@position\caption@fixedposition} +\let\caption@@begin\@gobble% new v3.0a +\let\caption@@end\@empty% new v3.0a +\newcommand*\caption@preparelof[1]{% changed v3.0b + \caption@ifbool{lof}% + {\def\caption@tempa{#1}}% + {\let\caption@tempa\@empty}% + \ifx\caption@tempa\@empty + \def\addcontentsline##1##2##3{}% + \fi} +\caption@ifpkt@caption{ + \renewcommand\@makecaption[2]{% + \caption@iftop{\vskip\belowcaptionskip}{\vskip\abovecaptionskip}% + \ifnum\caption@debug>1 % + \llap{$\caption@iftop\downarrow\uparrow$ }% + \fi + \caption@@make{#1}{#2}% + \caption@iftop{\vskip\abovecaptionskip}{\vskip\belowcaptionskip}} + \AtBeginDocument{% + \@ifundefined{cc@caption}{% + \def\caption@caption#1{% + \@ifstar{\global\caption@startrue\@ifnextchar[{#1}{#1[]}}{#1}}% + \let\caption@old\caption + \def\caption{\caption@caption\caption@old}% + \let\caption@@old\@caption + \long\def\@caption#1[#2]#3{% + \caption@beginex{#1}{#2}% + \caption@@old{#1}[{#2}]{#3}% + \caption@end}% + }{% + \PackageInfo{caption}{captcont package v2.0 detected}% + \def\caption@caption#1{#1}% added v3.0c + }% + }}{} +\AtEndOfPackage{\let\caption@ifpkt@caption\@undefined}% bugfixed v3.0a +\newcommand*\caption@ifpackage[2]{% + \let\next\@gobble + \caption@ifpkt@caption{% + \caption@ifbool{pkt@#1}{% + \@ifundefined{#2}% + {\let\next\AtBeginDocument}% + {\let\next\@firstofone}}{}% + \ifodd\caption@debug\relax + \edef\caption@tempa{% + \caption@ifbool{pkt@#1}{% + \@ifundefined{#2}{AtBeginDocument}{firstofone}% + }{gobble}}% + \PackageInfo{caption}{#1 = \caption@ifbool{pkt@#1}{1}{0} % + (\@ifundefined{#2}{not }{}loaded -> \caption@tempa)}% + \fi + }{}% + \@nameundef{caption@ifpkt@#1}% bugfixed v3.0a + \next} +\AtEndOfPackage{\let\caption@ifpackage\@undefined} +\def\caption@setfloatposition{% + \caption@setposition{\@fs@iftopcapt t\else b\fi}} +\caption@ifpackage{float}{float@caption}{% + \ifx\float@caption\relax + \else + \PackageInfo{caption}{float package v1.2 (or newer) detected}% + \let\caption@of@float\@gobble + \renewcommand*\caption@of[2]{% + \@ifundefined{fst@#2}{}{% + \let\caption@of@float\@firstofone + \@nameuse{fst@#2}\@float@setevery{#2}}% + \def\@captype{#2}#1}% + \renewcommand*\caption@floatname[1]{% + \@nameuse{\@ifundefined{fname@#1}{#1name}{fname@#1}}}% + \let\caption@@float\float@caption + \long\def\float@caption#1[#2]#3{% + \caption@beginex{#1}{#2}% + \let\@fs@capt\caption@@make + \caption@@float{#1}[{#2}]{#3}% + \caption@of@float{% + \def\caption@@make##1##2{\unvbox\@floatcapt}% + \@makecaption{}{}}% + \caption@end}% + \renewcommand*\caption@setfloattype[1]{% improved v3.0a + \caption@fixfloat@c{#1}% + \expandafter\ifx\csname @float@c@#1\endcsname\float@caption + \expandafter\let\expandafter\caption@fst\csname fst@#1\endcsname + \edef\caption@fst{\noexpand\string\expandafter\noexpand\caption@fst}% + \edef\caption@fst{\noexpand\@gobblefour\caption@fst}% + \@ifundefined{caption@sty@\caption@fst}{}{\caption@setstyle\caption@fst}% + \caption@setfloatposition% changed v3.0b + \fi + \caption@settype{#1}}% + \let\caption@float\caption + \def\caption{% + \ifx\@captype\@undefined + \@latex@error{\noexpand\caption outside float}\@ehd + \expandafter\@gobble + \else + \caption@fixfloat@c\@captype + \fi + \caption@float}% + \def\caption@fixfloat@c#1{% + \expandafter\let\expandafter\caption@tempa\csname @float@c@#1\endcsname + \ifx\caption@tempa\relax + \else\ifx\caption@tempa\float@caption + \else\ifx\caption@tempa\@caption + \else\ifx\caption@tempa\caption@@float + \ifodd\caption@debug\relax + \PackageInfo{caption}{\protect\@float@c@#1\space := \protect\float@caption}% + \fi + \expandafter\let\csname @float@c@#1\endcsname\float@caption + \else + \ifodd\caption@debug\relax + \PackageInfo{caption}{\protect\@float@c@#1\space := \protect\@caption}% + \fi + \expandafter\let\csname @float@c@#1\endcsname\@caption + \fi\fi\fi\fi}% + \fi} +\caption@ifbool{ruled}{}{% + \DeclareCaptionStyle{ruled}{labelfont=bf,labelsep=space}} +\let\caption@ifruled\@undefined +\caption@ifpackage{listings}{lst@MakeCaption}{% + \ifx\lst@MakeCaption\relax + \else + \PackageInfo{caption}{listings package v1.2 (or newer) detected}% + \let\caption@lst@MakeCaption\lst@MakeCaption + \def\lst@MakeCaption#1{% + \let\caption@setfloattype\caption@settype + \def\caption@autoposition{\caption@setposition{#1}}% + \caption@begin{lstlisting}% + \caption@lst@MakeCaption{#1}% + \caption@end}% + \fi} +\caption@ifpackage{longtable}{LT@makecaption}{% + \ifx\LT@makecaption\relax + \else + \PackageInfo{caption}{longtable package v3.15 (or newer) detected}% + \def\LT@makecaption#1#2#3{% + \LT@mcol\LT@cols c{\hbox to\z@{\hss\parbox[t]\linewidth{% + \caption@letfloattype{longtable}% + \caption@begin{table}% + \ifdim\LTcapwidth=4in \else + \caption@setwidth\LTcapwidth + \fi + \caption@startrue#1\caption@starfalse + \caption@@make{#2}{#3}% + \endgraf\vskip\baselineskip + \caption@end}% + \hss}}}% + \fi} +\caption@ifpackage{rotating}{@rotcaption}{% + \ifx\@rotcaption\relax + \else + \PackageInfo{caption}{rotating package v2.0 (or newer) detected}% + \let\caption@rot\rotcaption + \def\rotcaption{\caption@caption\caption@rot}% + \let\caption@@rot\@rotcaption + \long\def\@rotcaption#1[#2]#3{% + \caption@beginex{#1}{#2}% + \caption@@rot{#1}[{#2}]{#3}% + \caption@end}% + \long\def\@makerotcaption#1#2{% + \rotatebox{90}{% + \begin{minipage}{.8\textheight}% + \caption@@make{#1}{#2}% + \end{minipage}% + }\par + \hspace{12pt}}% + \fi} +\caption@ifpackage{sidecap}{endSC@FLOAT}{% + \ifx\endSC@FLOAT\relax + \else + \PackageInfo{caption}{sidecap package v1.4d (or newer) detected}% + \let\SC@caption=\caption + \let\caption@SC@zfloat\SC@zfloat + \def\SC@zfloat#1#2#3[#4]{% + \caption@SC@zfloat{#1}{#2}{#3}[#4]% + \global\let\SC@CAPsetup\@empty + \renewcommand\captionsetup[1]{\g@addto@macro\SC@CAPsetup{,##1}}% + \let\caption@old\caption + \def\caption{\caption@caption\caption@old}% + }% + \let\caption@endSC@FLOAT\endSC@FLOAT + \def\endSC@FLOAT{% + \caption@setmargin\z@ + \@ifundefined{SC@justify}{}{% + \ifx\SC@justify\@empty\else + \let\caption@hj\SC@justify + \let\SC@justify\@empty + \fi}% + \caption@esetup\SC@CAPsetup + \caption@letfloattype{SC\@captype}% + \caption@endSC@FLOAT}% + \fi} +\def\caption@setSTposition{% + \caption@setposition{\if@topcaption t\else b\fi}} +\caption@ifpackage{supertabular}{ST@caption}{% + \ifx\ST@caption\relax + \else + \PackageInfo{caption}{supertabular package detected}% + \let\caption@ST\ST@caption + \long\def\ST@caption#1[#2]#3{\par% bugfixed v3.0a + \caption@letfloattype{supertabular}% + \let\caption@fixposition\caption@setSTposition + \caption@beginex{#1}{#2}% + \addcontentsline{\csname ext@#1\endcsname}{#1}% + {\protect\numberline{% + \csname the#1\endcsname}{\ignorespaces #2}}% + \@parboxrestore + \normalsize + \@makecaption{\csname fnum@#1\endcsname}{\ignorespaces #3}\par + \caption@end}% + \fi} +\AtBeginDocument{\let\scr@caption\caption} +\endinput +%% +%% End of file `caption.sty'. From landa at grass.itc.it Mon Apr 16 17:14:55 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Mon Apr 16 17:14:57 2007 Subject: [grass-addons] r514 - trunk/grassaddons/gui/gui_modules Message-ID: <200704161514.l3GFEtqd012439@grass.itc.it> Author: landa Date: 2007-04-16 17:14:55 +0200 (Mon, 16 Apr 2007) New Revision: 514 Modified: trunk/grassaddons/gui/gui_modules/debug.py Log: inor bugfix Modified: trunk/grassaddons/gui/gui_modules/debug.py =================================================================== --- trunk/grassaddons/gui/gui_modules/debug.py 2007-04-16 14:41:50 UTC (rev 513) +++ trunk/grassaddons/gui/gui_modules/debug.py 2007-04-16 15:14:55 UTC (rev 514) @@ -43,22 +43,17 @@ def msg (self, level, message): self._update_level() - if self.debuglevel > 0 and level >= self.debuglevel: - print "D%d/%d: %s" % (level, level, message) + if self.debuglevel > 0 and level > 0 and level <= self.debuglevel: + print "GUI D%d/%d: %s" % (level, level, message) +# Debug instance +Debug = Debug() # testing if __name__ == "__main__": import cmd cmd.Command (cmd="g.gisenv set=DEBUG=3") - - for unit in range (2): - if unit == 1: - cmd.Command (cmd="g.gisenv set=DEBUG=0") - - reload (grassenv) # reload GRASS environments ! - debug = Debug() - - print "DEBUG=%d" % debug.debuglevel - for level in range (4): - debug.msg (level, "message level=%d" % level) + reload (grassenv) # reload GRASS environments ! + + for level in range (4): + Debug.msg (level, "message level=%d" % level) From landa at grass.itc.it Mon Apr 16 18:25:50 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Mon Apr 16 18:25:51 2007 Subject: [grass-addons] r515 - trunk/grassaddons/gui/gui_modules Message-ID: <200704161625.l3GGPoFq013063@grass.itc.it> Author: landa Date: 2007-04-16 18:25:50 +0200 (Mon, 16 Apr 2007) New Revision: 515 Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py trunk/grassaddons/gui/gui_modules/render.py Log: fixing broken scripts/p.mon Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-16 15:14:55 UTC (rev 514) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-16 16:25:50 UTC (rev 515) @@ -14,8 +14,6 @@ * MapApp """ -DEBUG = False - # Authors: Michael Barton and Jachym Cepicky # COPYRIGHT: (C) 1999 - 2006 by the GRASS Development Team # Double buffered drawing concepts from the wxPython Cookbook @@ -30,6 +28,7 @@ import track import menuform from digit import Digit as Digit +from debug import Debug as Debug import images imagepath = images.__path__[0] @@ -53,8 +52,6 @@ else: icons = os.environ["GRASS_ICONPATH"] -#Map = render.Map() # instance of Map class to render GRASS display output to PPM file - # for cmdlinef cmdfilename = None @@ -69,7 +66,7 @@ global cmdfilename self.parent = parent - self.map = render.Map() # instance of Map class to render GRASS display output to PPM file + self.map = Map self.cmdfile = open(cmdfilename,"r") def run(self): @@ -121,10 +118,7 @@ dispcmd[key] = value - if DEBUG: - print "Command.run(): ", - print "opacity=%d name=%s mapset=%s" % (opacity, name, mapset), - print dispcmd + Debug.msg (3, "Command.run(): opacity=%d name=%s mapset=%s, cmd=%s" % (opacity, name, mapset, dispcmd)) if action == "d.rast": self.map.AddRasterLayer(name=name, @@ -494,8 +488,7 @@ """ - if DEBUG: - print "Buffered Window.UpdateMap(%s): render=%s" % (img, self.render) + Debug.msg (3, "BufferedWindow.UpdateMap(%s): render=%s" % (img, self.render)) if self.render: # render new map images @@ -532,9 +525,10 @@ self.resize = False # update statusbar + #Debug.msg (3, "BufferedWindow.UpdateMap(%s): region=%s" % self.Map.region) self.parent.statusbar.SetStatusText("Extents: %d(W)-%d(E), %d(N)-%d(S)" % - (self.Map.region["w"], self.Map.region["e"], - self.Map.region["n"], self.Map.region["s"]), 0) + (self.Map.region["w"], self.Map.region["e"], + self.Map.region["n"], self.Map.region["s"]), 0) def EraseMap(self): """ @@ -966,6 +960,8 @@ Map -- instance of render.Map """ + Debug.msg (1, "MapFrame.__init__()") + wx.Frame.__init__(self, parent, id, title, pos, size, style) # most of the thime, this will be the gis manager @@ -1000,7 +996,7 @@ # self.statusbar = self.CreateStatusBar(number=2, style=0) self.statusbar.SetStatusWidths([-2, -1]) - map_frame_statusbar_fields = ["Extents: %d(W)-%d(E), %d(N)-%d(S)" % + map_frame_statusbar_fields = ["Extents: %d(W)-%d(E), %d(N)-%d(S)" % (self.Map.region["w"], self.Map.region["e"], self.Map.region["n"], self.Map.region["s"]), "%s,%s" %(None, None)] @@ -1116,8 +1112,10 @@ """ # change bookcontrol page to page associted with display - pgnum = self.layerbook.GetPageIndex(self.page) - if pgnum > -1: self.layerbook.SetSelection(pgnum) + if self.page: + pgnum = self.layerbook.GetPageIndex(self.page) + if pgnum > -1: + self.layerbook.SetSelection(pgnum) event.Skip() def OnMotion(self, event): @@ -1283,11 +1281,13 @@ """ pgnum = None self.Map.Clean() - pgnum = self.layerbook.GetPageIndex(self.page) + if self.page: + pgnum = self.layerbook.GetPageIndex(self.page) + if pgnum > -1: + self.layerbook.DeletePage(pgnum) + self.Destroy() - if pgnum > -1: self.layerbook.DeletePage(pgnum) - def getRender(self): """ returns the current instance of render.Map() @@ -1769,7 +1769,12 @@ def OnInit(self): wx.InitAllImageHandlers() - self.mapFrm = MapFrame(parent=None, id=wx.ID_ANY) + if __name__ == "__main__": + Map = render.Map() # instance of Map class to render GRASS display output to PPM file + else: + Map = None + + self.mapFrm = MapFrame(parent=None, id=wx.ID_ANY, Map=Map) #self.SetTopWindow(Map) self.mapFrm.Show() Modified: trunk/grassaddons/gui/gui_modules/render.py =================================================================== --- trunk/grassaddons/gui/gui_modules/render.py 2007-04-16 15:14:55 UTC (rev 514) +++ trunk/grassaddons/gui/gui_modules/render.py 2007-04-16 16:25:50 UTC (rev 515) @@ -6,12 +6,12 @@ import os,sys,glob import utils +from debug import Debug as Debug # Authors : Michael Barton, Jachym Cepicky, Martin Landa # # COPYRIGHT:(C) 1999 - 2007 by the GRASS Development Team -DEBUG = False class GRASSLayer: """ @@ -56,10 +56,8 @@ self.grassLayer = GRASSLayer(dispcmd) - if DEBUG: - print "MapLayer.__init__(): name=%s, mapset=%s, opacity=%d" % \ - (name, mapset, opacity), - print dispcmd + Debug.msg (1, "MapLayer.__init__(): name=%s, mapset=%s, opacity=%d, %s" % + (name, mapset, opacity, dispcmd)) gtemp = utils.GetTempfile() self.maskfile = gtemp + ".pgm" @@ -81,9 +79,7 @@ else: self.cmd += " -%s" % key - if DEBUG: - print "MapLayer.__renderRasterLayer():", - print self.cmd + Debug.msg (3, "MapLayer.__renderRasterLayer(): %s" % self.cmd) except StandardError, e: sys.stderr.write("Could not render raster layer <%s>: %s\n" %\ @@ -105,9 +101,7 @@ else: self.cmd += " -%s" % key - if DEBUG: - print "MapLayer.__renderVectorLayer():", - print self.cmd + Debug.msg (3, "MapLayer.__renderVectorLayer(): %s" % self.cmd) except StandardError, e: sys.stderr.write("Could not render vector layer <%s>: %s\n" %\ @@ -206,7 +200,7 @@ # if not self.cmd: sys.stderr.write("Could not render layer <%s> with command: #%s#" %\ - (self.name, self.cmd)) + (self.name, self.cmd)) return None if os.system(self.cmd): @@ -569,8 +563,7 @@ os.environ["GRASS_WIDTH"] = str(self.width) os.environ["GRASS_HEIGHT"] = str(self.height) - if DEBUG: - print ("Map.Render() force=%s" % (force)) + Debug.msg (3, "Map.Render() force=%s" % (force)) try: # render overlays @@ -591,8 +584,6 @@ # render map layers for layer in self.layers: - if DEBUG: - print "rendering", layer.name # skip if hidden or not active if layer.active == False or layer.hidden == True: continue @@ -610,8 +601,7 @@ maps.append(layer.mapfile) masks.append(layer.maskfile) opacities.append(str(layer.opacity)) - if DEBUG: - print "rendered", layer.name + Debug.msg (3, "Map.Render() layer=%s " % layer.name) # make arrays to strings mapstr = ",".join(maps) @@ -626,7 +616,7 @@ " width=" + str(self.width) + \ " height=" + str(self.height) + \ " output=" + self.mapfile - + # render overlays os.unsetenv("GRASS_REGION") @@ -639,6 +629,8 @@ sys.stderr.write("Could not run g.pnmcomp\n") raise Exception (compcmd) + #Debug.msg (3, "Map.Render() file=%s,%s" % (self.mapfile, self.ovlist)) + return self.mapfile, self.ovlist except Exception, e: From landa at grass.itc.it Mon Apr 16 18:40:23 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Mon Apr 16 18:40:24 2007 Subject: [grass-addons] r516 - in trunk/grassaddons/gui: . gui_modules Message-ID: <200704161640.l3GGeNE4013091@grass.itc.it> Author: landa Date: 2007-04-16 18:40:22 +0200 (Mon, 16 Apr 2007) New Revision: 516 Modified: trunk/grassaddons/gui/gui_modules/toolbars.py trunk/grassaddons/gui/wxgrass Log: enable DigitToolbar Modified: trunk/grassaddons/gui/gui_modules/toolbars.py =================================================================== --- trunk/grassaddons/gui/gui_modules/toolbars.py 2007-04-16 16:25:50 UTC (rev 515) +++ trunk/grassaddons/gui/gui_modules/toolbars.py 2007-04-16 16:40:22 UTC (rev 516) @@ -183,7 +183,7 @@ self.mapcontent = map self.parent = parent - self.icons = os.path.join (icons, "v.digit") + self.icons = os.path.join (os.getenv("GISBASE"), "etc/v.digit") # selected map to digitize self.layerID = -1 Modified: trunk/grassaddons/gui/wxgrass =================================================================== --- trunk/grassaddons/gui/wxgrass 2007-04-16 16:25:50 UTC (rev 515) +++ trunk/grassaddons/gui/wxgrass 2007-04-16 16:40:22 UTC (rev 516) @@ -10,7 +10,7 @@ if [ "$SYSTEM" = "Darwin" ] ; then pythonw "$GISBASE/etc/wx/wxgui.py" -name wxgui_py else - python "$GISBASE/etc/wx/wxgui.py" -name wxgui_py + python "$GISBASE/etc/wx/wxgui_silk.py" -name wxgui_py fi exit 0 From landa at grass.itc.it Mon Apr 16 18:42:07 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Mon Apr 16 18:42:09 2007 Subject: [grass-addons] r517 - trunk/grassaddons/gui Message-ID: <200704161642.l3GGg710013111@grass.itc.it> Author: landa Date: 2007-04-16 18:42:07 +0200 (Mon, 16 Apr 2007) New Revision: 517 Modified: trunk/grassaddons/gui/wxgrass Log: revert change Modified: trunk/grassaddons/gui/wxgrass =================================================================== --- trunk/grassaddons/gui/wxgrass 2007-04-16 16:40:22 UTC (rev 516) +++ trunk/grassaddons/gui/wxgrass 2007-04-16 16:42:07 UTC (rev 517) @@ -10,7 +10,7 @@ if [ "$SYSTEM" = "Darwin" ] ; then pythonw "$GISBASE/etc/wx/wxgui.py" -name wxgui_py else - python "$GISBASE/etc/wx/wxgui_silk.py" -name wxgui_py + python "$GISBASE/etc/wx/wxgui.py" -name wxgui_py fi exit 0 From calvelo at grass.itc.it Mon Apr 16 20:12:37 2007 From: calvelo at grass.itc.it (calvelo@grass.itc.it) Date: Mon Apr 16 20:12:38 2007 Subject: [grass-addons] r518 - trunk/grassaddons/gui/gui_modules Message-ID: <200704161812.l3GICbOK014460@grass.itc.it> Author: calvelo Date: 2007-04-16 20:12:28 +0200 (Mon, 16 Apr 2007) New Revision: 518 Modified: trunk/grassaddons/gui/gui_modules/grass-interface.dtd trunk/grassaddons/gui/gui_modules/menuform.py Log: - fix for DTD Modified: trunk/grassaddons/gui/gui_modules/grass-interface.dtd =================================================================== --- trunk/grassaddons/gui/gui_modules/grass-interface.dtd 2007-04-16 16:42:07 UTC (rev 517) +++ trunk/grassaddons/gui/gui_modules/grass-interface.dtd 2007-04-16 18:12:28 UTC (rev 518) @@ -126,7 +126,7 @@ How to set up rules for that (same problem for )? --> - + Modified: trunk/grassaddons/gui/gui_modules/menuform.py =================================================================== --- trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-16 16:42:07 UTC (rev 517) +++ trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-16 18:12:28 UTC (rev 518) @@ -31,9 +31,9 @@ # # - verify option value types # - add tooltips -# - use DOM instead of SAX !!! +# - use DOM instead of SAX """ -__version__ ="$Revision $" +__version__ ="$Revision$" import wx import sys @@ -779,7 +779,7 @@ self.OnUpdateValues() def buildCmd(self, ignoreErrors = False): - """Produce an array of command ame and arguments for feeding + """Produce an array of command name and arguments for feeding into some execve-like command processor. If ignoreErrors==True then it will return whatever has been @@ -837,9 +837,6 @@ """Stand-alone GRASS command GUI""" def __init__(self, grass_task): self.grass_task = grass_task -#XXX from pprint import pprint -# pprint( self.grass_task.params ) - wx.App.__init__(self) def OnInit(self): From calvelo at grass.itc.it Mon Apr 16 22:38:05 2007 From: calvelo at grass.itc.it (calvelo@grass.itc.it) Date: Mon Apr 16 22:38:07 2007 Subject: [grass-addons] r519 - trunk/grassaddons/gui/gui_modules Message-ID: <200704162038.l3GKc5GG015312@grass.itc.it> Author: calvelo Date: 2007-04-16 22:37:56 +0200 (Mon, 16 Apr 2007) New Revision: 519 Modified: trunk/grassaddons/gui/gui_modules/menuform.py Log: Isolated grassTask from GUI: - need to define a nice API for it, though, - it is currently used through get_param(), get_flag() and getCmd(); - it is initialized directly from a string containing a GRASS command, so the interface-description parser is now hidden beneath grassTask. Modified: trunk/grassaddons/gui/gui_modules/menuform.py =================================================================== --- trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-16 18:12:28 UTC (rev 518) +++ trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-16 20:37:56 UTC (rev 519) @@ -160,17 +160,60 @@ class grassTask: """This class holds the structures needed for both filling by the parser and - use by the interface constructor.""" - def __init__(self): + use by the interface constructor. + + Use as either grassTask() for empty definition or grassTask( "grass.command" ) + for parsed filling.""" + def __init__(self, grassModule = None): self.name = _('unknown') self.params = [] self.description = '' self.flags = [] + if grassModule is not None: + xml.sax.parseString( getInterfaceDescription( grassModule ) , processTask( self ) ) - def buildCmd(self): # TODO: It should be this class' responsibility to build the command, not the gui's. - pass + def get_param( self, aParam ): + """Find and return a param by name.""" + for p in self.params: + if p['name'] == aParam: + return p + raise ValueError, "Parameter ot found" + def get_flag( self, aFlag ): + """Find and return a flag by name.""" + for f in self.flags: + if f['name'] == aFlag: + return f + raise ValueError, "Falg not found" + + def getCmd(self, ignoreErrors = False): + """Produce an array of command name and arguments for feeding + into some execve-like command processor. + If ignoreErrors==True then it will return whatever has been + built so far, even though it would not be a correct command + for GRASS.""" + cmd = [self.name] + errors = 0 + errStr = "" + + for flag in self.flags: + if 'value' in flag and flag['value']: + cmd += [ '-' + flag['name'] ] + for p in self.params: + if p.get('value','') == '' and p.get('required','no') != 'no': + cmd += [ '%s=%s' % ( p['name'], _('') ) ] + errStr += _("Parameter %s (%s) is missing\n") % ( p['name'], p['description'] ) + errors += 1 + if p.get('value','') != '' and p['value'] != p.get('default','') : + cmd += [ '%s=%s' % ( p['name'], p['value'] ) ] + if errors and not ignoreErrors: + raise ValueError, errStr + return None + + return cmd + + class processTask(HandlerBase): """A SAX handler for the --interface-description output, as defined in grass-interface.dtd. Extend or modify this and the @@ -778,48 +821,23 @@ porf[ 'value' ] = me.GetValue() self.OnUpdateValues() - def buildCmd(self, ignoreErrors = False): - """Produce an array of command name and arguments for feeding - into some execve-like command processor. - If ignoreErrors==True then it will return whatever has been - built so far, even though it would not be a correct command - for GRASS.""" - cmd = [self.task.name] - errors = 0 - errStr = "" - dcmd_params = {} - - for flag in self.task.flags: - if 'value' in flag and flag['value']: - cmd += [ '-' + flag['name'] ] - for p in self.task.params: - if p.get('value','') == '' and p.get('required','no') != 'no': - cmd += [ p['name'] + '=' + _('')] - errStr += _("Parameter %s (%s) is missing\n") % ( p['name'], p['description'] ) - errors += 1 - if p.get('value','') != '' and p['value'] != p.get('default','') : - cmd += [ p['name'] + '=' + p['value'] ] - if errors and not ignoreErrors: - self.OnError(errStr) - return None - - return cmd - def createCmd( self, ignoreErrors = False ): """Produce a command line string for feeding into GRASS. If ignoreErrors==True then it will return whatever has been built so far, even though it would not be a correct command for GRASS.""" - return ' '.join( self.buildCmd( ignoreErrors=ignoreErrors ) ) + try: + cmd = ' '.join( self.task.getCmd( ignoreErrors=ignoreErrors ) ) + except ValueError, err: + dlg = wx.MessageDialog(self, str(err), _("Error"), wx.OK | wx.ICON_ERROR) + dlg.ShowModal() + dlg.Destroy() + cmd = '' + return cmd - def OnError(self, errMsg): - dlg = wx.MessageDialog(self, errMsg, _("Error"), wx.OK | wx.ICON_ERROR) - dlg.ShowModal() - dlg.Destroy() - def getInterfaceDescription( cmd ): """Returns the XML description for the GRASS cmd. @@ -890,12 +908,17 @@ print _("usage: %s ") % sys.argv[0] sys.exit() if sys.argv[1] != 'test': - grass_task = grassTask() - handler = processTask(grass_task) - xml.sax.parseString( getInterfaceDescription( sys.argv[1] ) , handler ) - app = GrassGUIApp( grass_task ) - app.MainLoop() + GrassGUIApp( grassTask( sys.argv[1] ) ).MainLoop() else: #Test + # Test grassTask from within a GRASS session + if os.getenv("GISBASE") != '': + task = grassTask( "d.vect" ) + task.get_param('map')['value'] = "map_name" + task.get_flag('v')['value'] = True + task.get_param('layer')['value'] = 12 + task.get_param('bcolor')['value'] = "red" + assert ' '.join( task.getCmd() ) == "d.vect -v map=map_name layer=12 bcolor=red" + # Test interface building with handmade grassTask task = grassTask() task.name = "TestTask" task.description = "This is a artificial grassTask() object intended for testing purposes" From calvelo at grass.itc.it Tue Apr 17 01:57:53 2007 From: calvelo at grass.itc.it (calvelo@grass.itc.it) Date: Tue Apr 17 01:57:56 2007 Subject: [grass-addons] r520 - trunk/grassaddons/gui/gui_modules Message-ID: <200704162357.l3GNvrCN017925@grass.itc.it> Author: calvelo Date: 2007-04-17 01:57:42 +0200 (Tue, 17 Apr 2007) New Revision: 520 Added: trunk/grassaddons/gui/gui_modules/toolbox.py Modified: trunk/grassaddons/gui/gui_modules/menuform.py Log: First shot at a simplified interface generator based on the QGIS GRASS plugin descriptions. Use with a .qgm file as argument. Lots TODO. This may have to wait for a stable qgm format in QGIS first... Modified: trunk/grassaddons/gui/gui_modules/menuform.py =================================================================== --- trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-16 20:37:56 UTC (rev 519) +++ trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-16 23:57:42 UTC (rev 520) @@ -177,14 +177,14 @@ for p in self.params: if p['name'] == aParam: return p - raise ValueError, "Parameter ot found" + raise ValueError, "Parameter not found : %s" % aParam def get_flag( self, aFlag ): """Find and return a flag by name.""" for f in self.flags: if f['name'] == aFlag: return f - raise ValueError, "Falg not found" + raise ValueError, "Flag not found : %s" % aFlag def getCmd(self, ignoreErrors = False): """Produce an array of command name and arguments for feeding @@ -209,7 +209,6 @@ cmd += [ '%s=%s' % ( p['name'], p['value'] ) ] if errors and not ignoreErrors: raise ValueError, errStr - return None return cmd @@ -564,22 +563,28 @@ self.task = task # Determine tab layout - sections = [ _('Main') ] + sections = [] is_section = {} - for task in self.task.params + self.task.flags: - if not task.has_key('guisection') or task['guisection']=='': - task['guisection'] = 'Options' + not_hidden = [ p for p in self.task.params + self.task.flags if not p.get( 'hidden','no' ) == 'yes' ] + for task in not_hidden: + if task.get( 'required','no' ) == 'yes': + # All required go into Main, even if they had defined another guisection + task['guisection'] = _( 'Main' ) + if task.get( 'guisection','' ) == '': + # Undefined guisections end up into Options + task['guisection'] = _( 'Options' ) if not is_section.has_key(task['guisection']): + # We do it like this to keep the original order, except for Main which goes first is_section[task['guisection']] = 1 - if task['guisection'] != _('Main'): # check for pre-existing parameters passed from layer tree - sections.append( task['guisection'] ) - there_is_main = False - for i in self.task.params+self.task.flags: - if i.has_key('required') and i['required'] == 'yes': - i['guisection'] = _('Main') - there_is_main = True - if not there_is_main: - sections = sections[1:] + sections.append( task['guisection'] ) + else: + is_section[ task['guisection'] ] += 1 + # Main goes first, Options goes second + for (newidx,content) in [ (0,_( 'Main' )), (1,_('Options')) ]: + if content in sections: + idx = sections.index( content ) + sections[idx:idx+1] = [] + sections[newidx:newidx] = [content] panelsizer = wx.BoxSizer(wx.VERTICAL) # Build notebook @@ -844,8 +849,9 @@ The DTD must be located in $GISBASE/etx/wx/gui_modules/grass-interface.dtd, otherwise the parser will not succeed.""" gmpath = os.getenv("GISBASE") + "/etc/wx/gui_modules" - cmd = cmd + r' --interface-description' - cmdout = os.popen(cmd, "r").read() + cmdout = os.popen(cmd + r' --interface-description', "r").read() + if not len(cmdout) > 0 : + raise IOError, "Couldn't make command %s provide its interface description." % cmd p = re.compile( '(grass-interface.dtd)') p.search( cmdout ) cmdout = p.sub( gmpath+r'/grass-interface.dtd', cmdout) Added: trunk/grassaddons/gui/gui_modules/toolbox.py =================================================================== --- trunk/grassaddons/gui/gui_modules/toolbox.py (rev 0) +++ trunk/grassaddons/gui/gui_modules/toolbox.py 2007-04-16 23:57:42 UTC (rev 520) @@ -0,0 +1,166 @@ +test_config = """ + + +

+ + +
+
+ + +
+ + + +""" +test_modules = {"g.gisenv":""" + + +""","r.to.vect.area":""" + + +""","r.to.vect.line":""" + + +"""} +import xml.dom.minidom +import menuform + +class handleQgisGrass: + + def __init__(self, qgisgrass): + modules = qgisgrass.childNodes[0].getElementsByTagName('modules')[0] + for child in modules.childNodes: + if child.localName == 'grass': + self.handleGrass( child ) + elif child.localName == 'section': + self.handleSection( child ) + + def handleSection( self,section ): + for child in section.getElementsByTagName('grass'): + self.handleGrass( child ) + + def handleGrass( self, grass ): + raise NotImplementedError + +class printQgisGrass( handleQgisGrass ): + def __init__(self, qgisgrass): + print "in qgisgrass" + handleQgisGrass.__init__( self, qgisgrass ) + + def handleSection( self,section ): + print "Section:",section.getAttribute('label') + handleQgisGrass.handleSection( self, section ) + + def handleGrass( self, grass ): + print "Command:",grass.getAttribute('name') + + +class wxQgisGrass( handleQgisGrass ): + pass + + +class handleQgisGrassModule: + + def __init__( self, qgisgrassmodule): + qgisgrassm = qgisgrassmodule.getElementsByTagName( "qgisgrassmodule" )[0] + self.handleAttributes( qgisgrassm ) + for inner in qgisgrassm.childNodes: + it = inner.localName + if it == 'option': + self.handleOption( inner ) + elif it == 'flag': + self.handleFlag( inner ) + + def handleAttributes( self, node ): + for (l,a) in node.attributes.items(): + self.handleAttribute( l, a, node ) + + def handleAttribute( self, label, value, parent ): + raise NotImplementedError + + def handleOption( self, option ): + self.handleAttributes( option ) + + def handleFlag( self, flag ): + self.handleAttributes( flag ) + +class printQgisGrassModule( handleQgisGrassModule ): + + def __init__( self, qgisgrassmodule): + print "in qgisgrassmodule" + handleQgisGrassModule.__init__( self, qgisgrassmodule ) + + def handleOption( self, opt ): + print "Option" + handleQgisGrassModule.handleOption( self, opt ) + print + + def handleFlag( self, flag ): + print "Flag" + handleQgisGrassModule.handleFlag( self, flag ) + + def handleAttribute( self, label, value, option ): + print "%s:%s" % (label, value) + +class wxQgisGrassModule( handleQgisGrassModule ): + def __init__(self, qgisgrassmodule, label='' ): + """qgisGrassModule is a string containing the .qgm xml file""" + self.task = None + self.label = label + self.description = '' + handleQgisGrassModule.__init__( self, qgisgrassmodule ) + self.task.description = self.description + menuform.GrassGUIApp( self.task ).MainLoop() + + def handleOption( self, opt, getit = menuform.grassTask.get_param ): + a = dict(opt.attributes.items()) + p = getit( self.task, a['key'] ) + p['hidden'] = 'no' # unhide params + p['guisection'] = _( 'Main' ) # this should be the only tab present in the end + if a.get('hidden','no') == 'yes': p['hidden'] = 'yes' #except when explicitly hidden + if a.has_key( 'answer' ): p['value'] = a.get('answer') + + def handleFlag( self, flag ): + self.handleOption( flag, getit = menuform.grassTask.get_flag ) + + def handleAttribute( self, label, value, option ): + if option.localName == 'qgisgrassmodule': + if label=='module': + self.task = menuform.grassTask( grassModule = value ) + for pf in self.task.params + self.task.flags: + pf['hidden'] = 'yes' + if label=='label': + self.description = value + +from sys import argv + +if __name__ == '__main__': + if len( argv ) != 2: + print "Usage: %s " % sys.argv[0] + else: + the_module = argv[1] + if the_module != 'test': + qgm = open( the_module ).read() + x = wxQgisGrassModule( xml.dom.minidom.parseString( qgm ), label = the_module ) + else: + # self test + config = xml.dom.minidom.parseString( test_config ) + printQgisGrass( config ) + print + for m in test_modules.keys(): + print m + module = xml.dom.minidom.parseString( test_modules[m] ) + printQgisGrassModule( module ) + print "----------------" + m = "r.to.vect.area" + x = wxQgisGrassModule( xml.dom.minidom.parseString( test_modules[ m ] ), label = m ) + From calvelo at grass.itc.it Tue Apr 17 02:10:17 2007 From: calvelo at grass.itc.it (calvelo@grass.itc.it) Date: Tue Apr 17 02:10:18 2007 Subject: [grass-addons] r521 - trunk/grassaddons/gui/gui_modules Message-ID: <200704170010.l3H0AHCc017969@grass.itc.it> Author: calvelo Date: 2007-04-17 02:10:09 +0200 (Tue, 17 Apr 2007) New Revision: 521 Modified: trunk/grassaddons/gui/gui_modules/menuform.py Log: - Properties for more useful $Revision $ thingie. Modified: trunk/grassaddons/gui/gui_modules/menuform.py =================================================================== --- trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-16 23:57:42 UTC (rev 520) +++ trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-17 00:10:09 UTC (rev 521) @@ -33,7 +33,7 @@ # - add tooltips # - use DOM instead of SAX """ -__version__ ="$Revision$" +__version__ ="$Revision $" import wx import sys Property changes on: trunk/grassaddons/gui/gui_modules/menuform.py ___________________________________________________________________ Name: svn:keywords + Date Author Revision Id From chemin at grass.itc.it Wed Apr 18 08:55:06 2007 From: chemin at grass.itc.it (chemin@grass.itc.it) Date: Wed Apr 18 08:55:09 2007 Subject: [grass-addons] r522 - trunk/grassaddons/gipe/r.evapo.MH Message-ID: <200704180655.l3I6t6Pc008777@grass.itc.it> Author: chemin Date: 2007-04-18 08:53:58 +0200 (Wed, 18 Apr 2007) New Revision: 522 Modified: trunk/grassaddons/gipe/r.evapo.MH/description.html Log: Added reference in r.evapo.MH/description.html Modified: trunk/grassaddons/gipe/r.evapo.MH/description.html =================================================================== --- trunk/grassaddons/gipe/r.evapo.MH/description.html 2007-04-17 00:10:09 UTC (rev 521) +++ trunk/grassaddons/gipe/r.evapo.MH/description.html 2007-04-18 06:53:58 UTC (rev 522) @@ -5,8 +5,8 @@

NOTES

Hargreaves GL, Hargreaves GH, Riley JP, 1985. Agricultural benefits for Senegal River Basin. Journal of Irrigation and Drainange Engineering, ASCE, 111(2):113-124. -Droogers P, Allen RG, 2001. Towards a simplified global reference evapotranspiration equation. Irrigation Science. - +Droogers P, Allen RG, 2002. Towards a simplified global reference evapotranspiration equation. Irrigation Science. +Droogers, P., and R.G. Allen. 2002. Estimating reference evapotranspiration under inaccurate data conditions. Irrigation and Drainage Systems 16: 33-45.

TODO

From landa at grass.itc.it Wed Apr 18 09:01:53 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Wed Apr 18 09:01:57 2007 Subject: [grass-addons] r523 - trunk/grassaddons/gui/gui_modules Message-ID: <200704180701.l3I71rMk008810@grass.itc.it> Author: landa Date: 2007-04-18 09:01:36 +0200 (Wed, 18 Apr 2007) New Revision: 523 Modified: trunk/grassaddons/gui/gui_modules/debug.py Log: enable debugging only GUI messages Modified: trunk/grassaddons/gui/gui_modules/debug.py =================================================================== --- trunk/grassaddons/gui/gui_modules/debug.py 2007-04-18 06:53:58 UTC (rev 522) +++ trunk/grassaddons/gui/gui_modules/debug.py 2007-04-18 07:01:36 UTC (rev 523) @@ -24,7 +24,7 @@ Usage: import cmd - cmd.Command (cmd="g.gisenv set=DEBUG=3") + cmd.Command (cmd="g.gisenv set=DEBUG=3") # only GUI debug messages DEBUG=GUI:3 import grassenv # or reload (grassenv) @@ -32,12 +32,20 @@ debug.msg (3, "message level=%d" % 3) """ def __init__(self): + # default level self.debuglevel = 0 + # update level self._update_level() def _update_level(self): if grassenv.env.has_key ("DEBUG"): - level = int (grassenv.env["DEBUG"]) + debug = grassenv.env["DEBUG"].strip() + try: + # only GUI debug messages [GUI:level] + level = int (debug[-1]) + except: + level = self.debuglevel + if self.debuglevel != level: self.debuglevel = level From landa at grass.itc.it Wed Apr 18 09:22:15 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Wed Apr 18 09:22:27 2007 Subject: [grass-addons] r524 - trunk/grassaddons/gui Message-ID: <200704180722.l3I7MFF9008864@grass.itc.it> Author: landa Date: 2007-04-18 09:21:14 +0200 (Wed, 18 Apr 2007) New Revision: 524 Modified: trunk/grassaddons/gui/README Log: explain debugging Modified: trunk/grassaddons/gui/README =================================================================== --- trunk/grassaddons/gui/README 2007-04-18 07:01:36 UTC (rev 523) +++ trunk/grassaddons/gui/README 2007-04-18 07:21:14 UTC (rev 524) @@ -2,7 +2,7 @@ ============================== AUTHORS: Jachym, Michael, MartinL -LAST CHANGE: 2007-04-06 +LAST CHANGE: $LastChangedDate$ --------------------------------------------------------------------- Requirements: @@ -108,4 +108,17 @@ displayed. You should be also able to store the display content as well as clear the display and start from scratch. +5 - DEBUGGING +To enable debugging messages use "GRASS standard way", e.g. + +$ g.gisenv set=DEBUG=3 + +This enables all debug messages (GRASS modules + GUI). GUI debug +messages starts with 'GUI', e.g. + + GUI D1/1: MapFrame.__init__() + +If you want to enable *only* GUI debug messages set DEBUG variable to 'GUI:level', e.g. + +$ g.gisenv set=DEBUG=GUI:3 From landa at grass.itc.it Wed Apr 18 09:26:40 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Wed Apr 18 09:26:44 2007 Subject: [grass-addons] r525 - trunk/grassaddons/gui Message-ID: <200704180726.l3I7Qeep008890@grass.itc.it> Author: landa Date: 2007-04-18 09:25:17 +0200 (Wed, 18 Apr 2007) New Revision: 525 Modified: trunk/grassaddons/gui/README Log: cosmetics Modified: trunk/grassaddons/gui/README =================================================================== --- trunk/grassaddons/gui/README 2007-04-18 07:21:14 UTC (rev 524) +++ trunk/grassaddons/gui/README 2007-04-18 07:25:17 UTC (rev 525) @@ -2,7 +2,7 @@ ============================== AUTHORS: Jachym, Michael, MartinL -LAST CHANGE: $LastChangedDate$ +$LastChangedDate$ --------------------------------------------------------------------- Requirements: Property changes on: trunk/grassaddons/gui/README ___________________________________________________________________ Name: svn:keywords + LastChangedDate From landa at grass.itc.it Wed Apr 18 09:31:26 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Wed Apr 18 09:31:29 2007 Subject: [grass-addons] r526 - trunk/grassaddons/gui/gui_modules Message-ID: <200704180731.l3I7VQ1L008931@grass.itc.it> Author: landa Date: 2007-04-18 09:30:48 +0200 (Wed, 18 Apr 2007) New Revision: 526 Modified: trunk/grassaddons/gui/gui_modules/menuform.py Log: cosmetics Modified: trunk/grassaddons/gui/gui_modules/menuform.py =================================================================== --- trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-18 07:25:17 UTC (rev 525) +++ trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-18 07:30:48 UTC (rev 526) @@ -33,7 +33,7 @@ # - add tooltips # - use DOM instead of SAX """ -__version__ ="$Revision $" +__version__ ="$Revision$" import wx import sys Property changes on: trunk/grassaddons/gui/gui_modules/menuform.py ___________________________________________________________________ Name: svn:keywords - Date Author Revision Id + Revision From landa at grass.itc.it Wed Apr 18 15:37:34 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Wed Apr 18 15:37:37 2007 Subject: [grass-addons] r527 - trunk/grassaddons/gui/gui_modules Message-ID: <200704181337.l3IDbYNB013326@grass.itc.it> Author: landa Date: 2007-04-18 15:37:32 +0200 (Wed, 18 Apr 2007) New Revision: 527 Modified: trunk/grassaddons/gui/gui_modules/cmd.py trunk/grassaddons/gui/gui_modules/digit.py trunk/grassaddons/gui/gui_modules/mapdisp.py trunk/grassaddons/gui/gui_modules/render.py trunk/grassaddons/gui/gui_modules/toolbars.py trunk/grassaddons/gui/gui_modules/wxgui_utils.py Log: * various changes * treat overlays as maplayers GetListOfLayers (l_type="overlay") * barscale code * cosmetics * more debug messages * black background during zooming fixed (temporaly) Modified: trunk/grassaddons/gui/gui_modules/cmd.py =================================================================== --- trunk/grassaddons/gui/gui_modules/cmd.py 2007-04-18 07:30:48 UTC (rev 526) +++ trunk/grassaddons/gui/gui_modules/cmd.py 2007-04-18 13:37:32 UTC (rev 527) @@ -17,6 +17,7 @@ """ import os, sys +from debug import Debug as Debug usePopenClass = True @@ -70,9 +71,11 @@ os.environ["GRASS_MESSAGE_FORMAT"] = "gui" # run command if not usePopenClass: + Debug.msg(3, "Command.__init__(): [popen3] cmd=%s" % cmd) (self.module_stdin, self.module_stdout, self.module_stderr) = \ os.popen3(self.cmd) else: + Debug.msg(3, "Command.__init__(): [Popen] cmd=%s" % cmd) self.module = subprocess.Popen(self.cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, Modified: trunk/grassaddons/gui/gui_modules/digit.py =================================================================== --- trunk/grassaddons/gui/gui_modules/digit.py 2007-04-18 07:30:48 UTC (rev 526) +++ trunk/grassaddons/gui/gui_modules/digit.py 2007-04-18 13:37:32 UTC (rev 527) @@ -1,5 +1,5 @@ """ -PACKAGE: digit +MODULE: digit CLASSES: * VEdit @@ -23,6 +23,7 @@ """ import cmd +from debug import Debug as Debug # class Digit: @@ -35,6 +36,8 @@ class VEdit(Digit): """ Prototype of digitization class based on v.edit command + + Note: This should be replaced by VDigit class. """ def AddPoint (self, map, x, y): """ @@ -42,16 +45,25 @@ """ addstring="""P 1 %f %f""" % (x,y) + + Debug.msg (3, "VEdit.AddPoint(): x=%f, y=%f" % (x, y)) + self._AddFeature (map=map, input=addstring) + + def _AddFeature (self, map, input): + """ + General method which adds feature to the vector map + """ command = """v.edit -n map=%s tool=add""" % (map) # run the command - vedit = cmd.Command(cmd=command, stdin=addstring) + vedit = cmd.Command(cmd=command, stdin=input) # result? if vedit.returncode == 0: pass else: - print "FAILURE" + print "FAILURE (%d)" % vedit.returncode + print "cmd=%s; input=%s" % (command, input) for msg in vedit.module_msg: print msg[1] Modified: trunk/grassaddons/gui/gui_modules/mapdisp.py =================================================================== --- trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-18 07:30:48 UTC (rev 526) +++ trunk/grassaddons/gui/gui_modules/mapdisp.py 2007-04-18 13:37:32 UTC (rev 527) @@ -189,12 +189,12 @@ # self.mapfile = None # image file to be rendered self.img = "" # wx.Image object (self.mapfile) - self.ovldict = {} # list of images for overlays + self.ovldict = {} # list of images for overlays self.ovlcoords = {} # positioning coordinates for decoration overlay + self.ovlchk = {} # showing/hiding decorations self.imagedict = {} # images and their PseudoDC ID's for painting and dragging self.crop = {} # coordinates to crop overlays to their data, indexed by image ID self.select = {} # selecting/unselecting decorations for dragging - self.ovlchk = {} # showing/hiding decorations self.textdict = {} # text, font, and color indexed by id self.currtxtid = None # PseudoDC id for currently selected text @@ -259,9 +259,11 @@ else: bg = wx.Brush(self.GetBackgroundColour()) pdc.SetBackground(bg) - pdc.Clear() + #pdc.Clear() #FIXME (to avoid black background) self.Refresh() + Debug.msg (3, "BufferedWindow.Draw(): id=%d, pdctype=%s, coord=%s" % (drawid, pdctype, coords)) + if pdctype == 'clear': # erase the display bg = wx.Brush(self.GetBackgroundColour()) pdc.SetBackground(bg) @@ -272,9 +274,9 @@ if pdctype == 'image': bitmap = wx.BitmapFromImage(img) -# if drawid in self.ovldict: -# w,h = self.ovldict[drawid][1] -# else: + # if drawid in self.ovldict: + # w,h = self.ovldict[drawid][1] + # else: w,h = bitmap.GetSize() pdc.DrawBitmap(bitmap, coords[0], coords[1], True) # draw the composite map pdc.SetIdBounds(drawid, (coords[0],coords[1],w,h)) @@ -410,57 +412,57 @@ Converts overlay files to wx.Image """ self.ovldict = {} - if self.Map.ovlist: - for ovlfile in self.Map.ovlist: - if os.path.isfile(ovlfile) and os.path.getsize(ovlfile): - img = wx.Image(ovlfile, wx.BITMAP_TYPE_ANY) + for overlay in self.Map.GetListOfLayers(l_type="overlay", l_active=True): + if os.path.isfile(overlay.mapfile) and os.path.getsize(overlay.mapfile): + img = wx.Image(overlay.mapfile, wx.BITMAP_TYPE_ANY) -# left = right = top = bottom = 0 -# breakout = False -# # auto-crop scales and legends -# for w in range(img.GetWidth()): # set left edge -# for h in range(img.GetHeight()-1): -# if img.IsTransparent(w,h) == False: -# left = w -# breakout = True -# break -# if breakout: -# breakout = False -# break -# for w in range(img.GetWidth()-1, 0, -1): # set right edge -# for h in range(img.GetHeight()-1): -# if img.IsTransparent(w,h) == False: -# right = w -# breakout = True -# break -# if breakout: -# breakout = False -# break -# for h in range(img.GetHeight()): # set top edge -# for w in range(left,right): -# if img.IsTransparent(w,h) == False: -# top = h -# breakout = True -# break -# if breakout: -# breakout = False -# break -# for h in range(img.GetHeight()-1, 0, - 1): # set top edge -# for w in range(left,right): -# if img.IsTransparent(w,h) == False: -# bottom = h -# breakout = True -# break -# if breakout: -# breakout = False -# break -# cropwidth = right - left -# cropheight = bottom - top - pdc_id = self.Map.ovlist.index(ovlfile) -# self.ovldict[pdc_id] = img,(cropwidth,cropheight) # image and cropping information for each overlay image - self.ovldict[pdc_id] = img # image information for each overlay image - self.imagedict[img] = pdc_id # set image PeudoDC ID + # left = right = top = bottom = 0 + # breakout = False + # # auto-crop scales and legends + # for w in range(img.GetWidth()): # set left edge + # for h in range(img.GetHeight()-1): + # if img.IsTransparent(w,h) == False: + # left = w + # breakout = True + # break + # if breakout: + # breakout = False + # break + # for w in range(img.GetWidth()-1, 0, -1): # set right edge + # for h in range(img.GetHeight()-1): + # if img.IsTransparent(w,h) == False: + # right = w + # breakout = True + # break + # if breakout: + # breakout = False + # break + # for h in range(img.GetHeight()): # set top edge + # for w in range(left,right): + # if img.IsTransparent(w,h) == False: + # top = h + # breakout = True + # break + # if breakout: + # breakout = False + # break + # for h in range(img.GetHeight()-1, 0, - 1): # set top edge + # for w in range(left,right): + # if img.IsTransparent(w,h) == False: + # bottom = h + # breakout = True + # break + # if breakout: + # breakout = False + # break + # cropwidth = right - left + # cropheight = bottom - top + pdc_id = self.Map.overlays.index(overlay) + # self.ovldict[pdc_id] = img,(cropwidth,cropheight) # image and cropping information for each overlay image + self.ovldict[pdc_id] = img # image information for each overlay image + self.imagedict[img] = pdc_id # set image PeudoDC ID + Debug.msg (3, "BufferedWindow.GetOverlay(): numberof=%d" % len(self.ovldict)) return self.ovldict @@ -488,7 +490,7 @@ """ - Debug.msg (3, "BufferedWindow.UpdateMap(%s): render=%s" % (img, self.render)) + Debug.msg (2, "BufferedWindow.UpdateMap(%s): render=%s" % (img, self.render)) if self.render: # render new map images @@ -938,8 +940,6 @@ pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE, toolbars=["map"], tree=None, notebook=None, gismgr=None, page=None, Map=None): - - """ Main map display window with toolbars, statusbar and DrawWindow @@ -960,7 +960,7 @@ Map -- instance of render.Map """ - Debug.msg (1, "MapFrame.__init__()") + Debug.msg (1, "MapFrame.__init__(): size=%d,%d" % (size[0], size[1])) wx.Frame.__init__(self, parent, id, title, pos, size, style) @@ -970,13 +970,18 @@ self.tree = tree # GIS Manager layer tree object self.page = page # Notebook page holding the layer tree self.layerbook = notebook #GIS Manager layer tree notebook + # available cursors + self.cursors = { + "default" : wx.StockCursor (wx.CURSOR_DEFAULT), + "cross" : wx.StockCursor (wx.CURSOR_CROSS), + "hand" : wx.StockCursor (wx.CURSOR_HAND) + } # - # Set the size + # Set the size & cursor # - self.SetClientSize((600, 475)) - - + self.SetClientSize(size) + # # Fancy gui # @@ -1005,9 +1010,9 @@ # d.barscale overlay added to rendering overlay list - self.Map.addOverlay(type=0, command='d.barscale', l_active=True, l_render=False) + self.Map.addOverlay(type=0, command='d.barscale', l_active=False, l_render=False) # d.barscale overlay added to rendering overlay list as placeholder for d.legend - self.Map.addOverlay(type=1, command='d.barscale', l_active=True, l_render=False) + self.Map.addOverlay(type=1, command='d.barscale', l_active=False, l_render=False) # # Init map display @@ -1018,7 +1023,8 @@ # self.MapWindow = DrawWindow(self) self.MapWindow = BufferedWindow(self, id = wx.ID_ANY, Map=self.Map, tree=self.tree) # initialize buffered DC self.MapWindow.Bind(wx.EVT_MOTION, self.OnMotion) - + self.MapWindow.SetCursor (self.cursors["cross"]) # default + # # Init zoomhistory # @@ -1041,15 +1047,6 @@ self._mgr.AddPane(self.MapWindow, wx.CENTER) self._mgr.Update() - # - # Create dictionary of available cursors - # - self.cursors = { - "default" : wx.StockCursor (wx.CURSOR_DEFAULT), - "cross" : wx.StockCursor (wx.CURSOR_CROSS), - "hand" : wx.StockCursor (wx.CURSOR_HAND) - } - def AddToolbar(self, name): """ Add defined toolbar to the window @@ -1151,7 +1148,7 @@ self.MapWindow.mouse['box'] = "point" # change the cursor - self.MapWindow.SetCursor (self.cursors["default"]) + self.MapWindow.SetCursor (self.cursors["cross"]) # default def OnZoomIn(self, event): """ @@ -1409,22 +1406,31 @@ else: params = '' + # get overlay images (overlay must be active) + if not self.Map.overlays[ovltype].active: + self.Map.changeOverlayActive(type=0, activ=True) + self.Map.Render(force=True) + ovldict = self.MapWindow.GetOverlay() - if id not in ovldict: return + + if id not in ovldict: + return + img = ovldict[id] - if id not in self.ovlcoords: self.ovlcoords[id] = [10,10] + if id not in self.ovlcoords: + self.ovlcoords[id] = [10,10] # Decoration overlay control dialog dlg = DecDialog(self, wx.ID_ANY, 'Scale and North arrow', size=(350, 200), - style=wx.DEFAULT_DIALOG_STYLE, - ovltype=ovltype, - cmd='d.barscale', - drawid=id, - checktxt = "Show/hide scale and arrow", - ctrltxt = "scale object", - params = params) - + style=wx.DEFAULT_DIALOG_STYLE, + ovltype=ovltype, + cmd='d.barscale', + drawid=id, + checktxt = "Show/hide scale and arrow", + ctrltxt = "scale object", + params = params) + dlg.CenterOnScreen() # If OK button pressed in decoration control dialog @@ -1432,8 +1438,8 @@ if val == wx.ID_OK: if self.ovlchk[id] == True: self.MapWindow.Draw(self.MapWindow.pdc, drawid=id, - img=img, pdctype='image', - coords=self.ovlcoords[id]) + img=img, pdctype='image', + coords=self.ovlcoords[id]) self.MapWindow.UpdateMap() dlg.Destroy() @@ -1448,11 +1454,20 @@ else: params = '' + # get overlay images (overlay must be active) + if not self.Map.overlays[ovltype].active: + self.Map.changeOverlayActive(type=0, activ=True) + self.Map.Render(force=True) + ovldict = self.MapWindow.GetOverlay() - if id not in ovldict: return + + if id not in ovldict: + return + img = ovldict[id] - if id not in self.ovlcoords: self.ovlcoords[id] = [10,10] + if id not in self.ovlcoords: + self.ovlcoords[id] = [10,10] # Decoration overlay control dialog dlg = DecDialog(self, wx.ID_ANY, 'Legend', size=(350, 200), @@ -1611,7 +1626,9 @@ box = wx.BoxSizer(wx.HORIZONTAL) self.chkbox = wx.CheckBox(self, wx.ID_ANY, checktxt) - if drawid in self.ovlchk: self.chkbox.SetValue(self.ovlchk[drawid]) + if not drawid in self.ovlchk: + self.ovlchk[drawid] = True + self.chkbox.SetValue(self.ovlchk[drawid]) box.Add(self.chkbox, 0, wx.ALIGN_CENTRE|wx.ALL, 5) sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5) @@ -1774,7 +1791,7 @@ else: Map = None - self.mapFrm = MapFrame(parent=None, id=wx.ID_ANY, Map=Map) + self.mapFrm = MapFrame(parent=None, id=wx.ID_ANY, Map=Map, size=(640,480)) #self.SetTopWindow(Map) self.mapFrm.Show() Modified: trunk/grassaddons/gui/gui_modules/render.py =================================================================== --- trunk/grassaddons/gui/gui_modules/render.py 2007-04-18 07:30:48 UTC (rev 526) +++ trunk/grassaddons/gui/gui_modules/render.py 2007-04-18 13:37:32 UTC (rev 527) @@ -1,18 +1,28 @@ """ -class GRASSLayer -class MapLayer -class Map +MODULE: render + +CLASSES: + * GRASSLayer + * MapLayer + * Map + +PURPOSE: Rendering + +AUTHORS: The GRASS Development Team + Michael Barton, Jachym Cepicky, Martin Landa + +COPYRIGHT: (C) 2006-2007 by the GRASS Development Team + This program is free software under the GNU General Public + License (>=v2). Read the file COPYING that comes with GRASS + for details. """ import os,sys,glob import utils + +import cmd from debug import Debug as Debug -# Authors : Michael Barton, Jachym Cepicky, Martin Landa -# -# COPYRIGHT:(C) 1999 - 2007 by the GRASS Development Team - - class GRASSLayer: """ This class stores GRASS layer metainformation @@ -56,14 +66,17 @@ self.grassLayer = GRASSLayer(dispcmd) - Debug.msg (1, "MapLayer.__init__(): name=%s, mapset=%s, opacity=%d, %s" % - (name, mapset, opacity, dispcmd)) + Debug.msg (3, "MapLayer.__init__(): type=%s, name=%s, mapset=%s, opacity=%d, active=%d %s" % + (type, name, mapset, opacity, active, dispcmd)) gtemp = utils.GetTempfile() self.maskfile = gtemp + ".pgm" - self.mapfile = gtemp + ".ppm" - self.ovlfile = gtemp + ".png" + if self.type == "overlay": + self.mapfile = gtemp + ".png" + else: + self.mapfile = gtemp + ".ppm" + def __renderRasterLayer(self): """ Stores d.rast command with all parameters in the self.cmd variable @@ -126,17 +139,21 @@ Stores overlay command with all parameters in the self.cmd variable """ + if not self.active: + return + try: if self.name != '': self.cmd = self.name + " --q" + Debug.msg (3, "MapLayer.__renderOverlay(): cmd=%s" % self.name) else: self.cmd = None - + except StandardError, e: sys.stderr.write("Could not render overlay <%s>: %s\n" %\ - (self.name, str(e))) + (self.name, str(e))) self.cmd = None - + def Render(self): """ Runs all d.* commands. @@ -155,9 +172,9 @@ # if self.type == 'overlay': - if not self.ovlfile: + if not self.mapfile: gtemp = utils.GetTempfile() - self.ovlfile = gtemp + ".png" + self.mapfile = gtemp + ".png" else: if not self.mapfile: gtemp = utils.GetTempfile() @@ -189,22 +206,22 @@ # # Start monitor # - if self.type == 'overlay': - os.environ["GRASS_PNGFILE"] = self.ovlfile - else: - os.environ["GRASS_PNGFILE"] = self.mapfile + + os.environ["GRASS_PNGFILE"] = self.mapfile os.environ["GRASS_RENDER_IMMEDIATE"] = "TRUE" # # execute command # if not self.cmd: - sys.stderr.write("Could not render layer <%s> with command: #%s#" %\ + sys.stderr.write("Cannot render layer <%s> with command: #%s#" %\ (self.name, self.cmd)) return None - - if os.system(self.cmd): + runcmd = cmd.Command(cmd=self.cmd) + if runcmd.returncode != 0: print "Could not execute '%s'" % (self.cmd) + for msg in runcmd.msg: + print msg[1] self.mapfile = None self.maskfile = None return None @@ -215,12 +232,8 @@ os.unsetenv("GRASS_PNGFILE") os.unsetenv("GRASS_RENDER_IMMEDIATE") - if self.type == 'overlay': - pass - return self.ovlfile - else: - return self.mapfile - + return self.mapfile + class Map: """ This class serves for rendering of output images. @@ -261,12 +274,11 @@ self.height = 400 # map height self.layers = [] # stack of available layer - self.overlays = [] + self.overlays = [] # stack of available overlays self.lookup = {} # lookup dictionary for tree items and layers self.env = {} # enviroment variables, like MAPSET, LOCATION_NAME, etc. self.verbosity = 0 self.mapfile = utils.GetTempfile() - self.ovlist = [] # self.renderRegion = { # "render" : True, # should the region be displayed? @@ -413,6 +425,7 @@ self.region['rows'] = self.width self.region['cols'] = self.height + Debug.msg (3, "Map.__adjustRegion(): %s" % self.region) def GetRegion(self): """ @@ -438,6 +451,7 @@ if tmpreg: os.environ["GRASS_REGION"] = tmpreg + Debug.msg (3, "Map.GetRegion(): %s" % region) return region def SetRegion(self): @@ -490,6 +504,7 @@ else: grass_region += key + ": " + self.wind[key] + "; " + Debug.msg (4, "Map.SetRegion(): %s" % grass_region) return grass_region except: @@ -497,13 +512,14 @@ def GetListOfLayers(self, l_type=None, l_active=None, l_hidden=None): """ - Returns list of layers of selected type or list of all layers. It + Returns list of layers (including overlays [l_type='overlay'] of + selected type or list of all layers. It is also possible to get list of active or hidden layers. Parameters: - l_type - layer type. raster/vector/wms/... - l_active - only layers with "active" attribute set to True or False - l_hidden - only layers with "hidden" attribute set to True or False + l_type - layer type, e.g. raster/vector/wms/overlay ... + l_active - only layers with 'active' attribute set to True or False + l_hidden - only layers with 'hidden' attribute set to True or False Returns: List of selected layers or None @@ -512,9 +528,7 @@ selected = [] # ["raster", "vector", "wms", ... ] - - for layer in self.layers: - + for layer in self.layers + self.overlays: # specified type only if l_type != None and layer.type != l_type: continue @@ -540,6 +554,7 @@ else: selected.append(layer) + Debug.msg (3, "Map.GetListOfLayers(): numberof=%d" % len(selected)) return selected def Render(self, force=False): @@ -563,29 +578,11 @@ os.environ["GRASS_WIDTH"] = str(self.width) os.environ["GRASS_HEIGHT"] = str(self.height) - Debug.msg (3, "Map.Render() force=%s" % (force)) - try: - # render overlays - self.ovlist = [] - for overlay in self.overlays: - if overlay == None or overlay.active == False: - continue - - # render if there is no mapfile - if overlay.ovlfile == None: - overlay.Render() - - # redraw layer content - if force: - if not overlay.Render(): - continue - self.ovlist.append(overlay.ovlfile) - # render map layers - for layer in self.layers: + for layer in self.layers + self.overlays: # skip if hidden or not active - if layer.active == False or layer.hidden == True: + if layer == None or layer.active == False or layer.hidden == True: continue # render if there is no mapfile @@ -598,11 +595,13 @@ continue # add image to compositing list - maps.append(layer.mapfile) - masks.append(layer.maskfile) - opacities.append(str(layer.opacity)) - Debug.msg (3, "Map.Render() layer=%s " % layer.name) + if layer.type != "overlay": + maps.append(layer.mapfile) + masks.append(layer.maskfile) + opacities.append(str(layer.opacity)) + Debug.msg (3, "Map.Render() type=%s, layer=%s " % (layer.type, layer.name)) + # make arrays to strings mapstr = ",".join(maps) maskstr = ",".join(masks) @@ -629,10 +628,10 @@ sys.stderr.write("Could not run g.pnmcomp\n") raise Exception (compcmd) - #Debug.msg (3, "Map.Render() file=%s,%s" % (self.mapfile, self.ovlist)) - - return self.mapfile, self.ovlist + Debug.msg (2, "Map.Render() force=%s file=%s" % (force, self.mapfile)) + return self.mapfile + except Exception, e: os.unsetenv("GRASS_REGION") @@ -1005,8 +1004,7 @@ return None def addOverlay(self, type, command, mapset=None, l_active=True, - l_hidden=False, l_opacity=1, l_render=False): - + l_hidden=False, l_opacity=1, l_render=False): """ Adds overlay (grid, barscale, others?) to list of overlays @@ -1017,13 +1015,12 @@ Returns: Added layer on success or None - """ + Debug.msg (2, "Map.addOverlay(): name=%s, mapset=%s, render=%d" % (command, mapset, l_render)) overlay = MapLayer(type="overlay", name=command, mapset=mapset, active=l_active, hidden=l_hidden, opacity=l_opacity) - # add maplayer to the list of layers self.overlays.append(overlay) # add item and layer to lookup dictionary @@ -1035,8 +1032,11 @@ return self.overlays[-1] def changeOverlay(self, type, command, mapset=None, l_active=True, - l_hidden=False, l_opacity=1, l_render=False): - + l_hidden=False, l_opacity=1, l_render=False): + """ + Change overlay properities + """ + overlay = MapLayer('overlay', command, mapset, l_active, l_hidden, l_opacity) @@ -1050,10 +1050,16 @@ return self.overlays[-1] def changeOverlayActive(self, type, activ): - overlay = self.overlays[type] - overlay.active = activ + """ + Change active status of overlay + """ + try: + overlay = self.overlays[type] + overlay.active = activ + Debug.msg (3, "Map.changeOverlayActive(): type=%d, active=%d" % (type, activ)) + except: + sys.stderr.write("Cannot change status of overlay index [%d]\n" % type) - def Clean(self): """ Go trough all layers and remove them from layer list Modified: trunk/grassaddons/gui/gui_modules/toolbars.py =================================================================== --- trunk/grassaddons/gui/gui_modules/toolbars.py 2007-04-18 07:30:48 UTC (rev 526) +++ trunk/grassaddons/gui/gui_modules/toolbars.py 2007-04-18 13:37:32 UTC (rev 527) @@ -11,6 +11,7 @@ #import wxgui_utils import cmd +from debug import Debug as Debug #icons= os.path.split(icons)[0] #icons= os.path.split(icons)[0] @@ -256,7 +257,7 @@ self.parent.Bind(wx.EVT_COMBOBOX, self.OnSelectMap, self.comboid) def OnAddPoint(self,event): - + Debug.msg (3, "DigitToolbar.OnAddPoint()") self.action="addpoint" #self.parent.MapWindow.mouse['box'] = "point" Modified: trunk/grassaddons/gui/gui_modules/wxgui_utils.py =================================================================== --- trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-18 07:30:48 UTC (rev 526) +++ trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-18 13:37:32 UTC (rev 527) @@ -65,10 +65,10 @@ # init associated map display self.mapdisplay = mapdisp.MapFrame(self, - id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, - style=wx.DEFAULT_FRAME_STYLE, - tree=self, notebook=self.notebook, gismgr=self.gismgr, page=self.treepg, - Map=self.Map) + id=wx.ID_ANY, pos=wx.DefaultPosition, size=(640,480), + style=wx.DEFAULT_FRAME_STYLE, + tree=self, notebook=self.notebook, gismgr=self.gismgr, page=self.treepg, + Map=self.Map) # title From landa at grass.itc.it Thu Apr 19 17:10:16 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Thu Apr 19 17:10:20 2007 Subject: [grass-addons] r528 - trunk/grassaddons/gui/gui_modules Message-ID: <200704191510.l3JFAGn0007315@grass.itc.it> Author: landa Date: 2007-04-19 17:10:15 +0200 (Thu, 19 Apr 2007) New Revision: 528 Modified: trunk/grassaddons/gui/gui_modules/cmd.py trunk/grassaddons/gui/gui_modules/menuform.py trunk/grassaddons/gui/gui_modules/render.py trunk/grassaddons/gui/gui_modules/wxgui_utils.py Log: menuform minor changes Modified: trunk/grassaddons/gui/gui_modules/cmd.py =================================================================== --- trunk/grassaddons/gui/gui_modules/cmd.py 2007-04-18 13:37:32 UTC (rev 527) +++ trunk/grassaddons/gui/gui_modules/cmd.py 2007-04-19 15:10:15 UTC (rev 528) @@ -83,11 +83,12 @@ close_fds=True) self.module_stdin = self.module.stdin self.module_stderr = self.module.stderr + self.module_stdout = self.module.stdout if stdin: self.module_stdin.write(stdin) self.module_stdin.close() - + os.environ["GRASS_MESSAGE_FORMAT"] = "text" try: @@ -102,6 +103,7 @@ else: self.returncode = None + Debug.msg (3, "Command(): cmd=%s, wait=%d, returncode=%d" % (self.cmd, wait, self.returncode)) def Run(self, verbose=False): """ Modified: trunk/grassaddons/gui/gui_modules/menuform.py =================================================================== --- trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-18 13:37:32 UTC (rev 527) +++ trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-19 15:10:15 UTC (rev 528) @@ -1,6 +1,16 @@ #! /usr/bin/python """ Construct simple wx.Python GUI from a GRASS command interface description. +Classes: + * testSAXContentHandler + * grassTask + * processTask + * helpPanel + * mainFrame + * cmdPanel + * GrassGUIApp + * GUI + # Copyright (C) 2000-2007 by the GRASS Development Team # Author: Jan-Oliver Wagner # improved by: Bernhard Reiter @@ -159,11 +169,13 @@ return 0 class grassTask: - """This class holds the structures needed for both filling by the parser and + """ + This class holds the structures needed for both filling by the parser and use by the interface constructor. - Use as either grassTask() for empty definition or grassTask( "grass.command" ) - for parsed filling.""" + Use as either grassTask() for empty definition or grassTask( 'grass.command' ) + for parsed filling. + """ def __init__(self, grassModule = None): self.name = _('unknown') self.params = [] @@ -173,30 +185,36 @@ xml.sax.parseString( getInterfaceDescription( grassModule ) , processTask( self ) ) def get_param( self, aParam ): - """Find and return a param by name.""" + """ + Find and return a param by name. + """ for p in self.params: if p['name'] == aParam: return p raise ValueError, "Parameter not found : %s" % aParam def get_flag( self, aFlag ): - """Find and return a flag by name.""" + """ + Find and return a flag by name. + """ for f in self.flags: if f['name'] == aFlag: return f raise ValueError, "Flag not found : %s" % aFlag def getCmd(self, ignoreErrors = False): - """Produce an array of command name and arguments for feeding + """ + Produce an array of command name and arguments for feeding into some execve-like command processor. - + If ignoreErrors==True then it will return whatever has been built so far, even though it would not be a correct command - for GRASS.""" + for GRASS. + """ cmd = [self.name] errors = 0 errStr = "" - + for flag in self.flags: if 'value' in flag and flag['value']: cmd += [ '-' + flag['name'] ] @@ -214,17 +232,20 @@ class processTask(HandlerBase): - """A SAX handler for the --interface-description output, as + """ + A SAX handler for the --interface-description output, as defined in grass-interface.dtd. Extend or modify this and the - DTD if the XML output of GRASS' parser is extended or modified.""" + DTD if the XML output of GRASS' parser is extended or modified. + """ def __init__(self, task_description): - self.inDescriptionContent = 0 - self.inDefaultContent = 0 - self.inValueContent = 0 - self.inParameter = 0 - self.inFlag = 0 - self.inGispromptContent = 0 - self.inGuisection = 0 + self.inLabelContent = False + self.inDescriptionContent = False + self.inDefaultContent = False + self.inValueContent = False + self.inParameter = False + self.inFlag = False + self.inGispromptContent = False + self.inGuisection = False self.task = task_description def startElement(self, name, attrs): @@ -233,7 +254,8 @@ self.task.name = attrs.get('name', None) if name == 'parameter': - self.inParameter = 1; + self.inParameter = True; + self.param_label = '' self.param_description = '' self.param_default = '' self.param_values = [] @@ -249,23 +271,27 @@ self.param_multiple = attrs.get('multiple', None) if name == 'flag': - self.inFlag = 1; + self.inFlag = True; self.flag_description = '' self.flag_default = '' self.flag_values = [] # Look for the flag name self.flag_name = attrs.get('name', None) + if name == 'label': + self.inLabelContent = True + self.label = '' + if name == 'description': - self.inDescriptionContent = 1 + self.inDescriptionContent = True self.description = '' if name == 'default': - self.inDefaultContent = 1 + self.inDefaultContent = True self.param_default = '' if name == 'value': - self.inValueContent = 1 + self.inValueContent = True self.value_tmp = '' if name == 'gisprompt': @@ -275,7 +301,7 @@ self.param_prompt = attrs.get('prompt', None) if name == 'guisection': - self.inGuisection = 1 + self.inGuisection = True self.param_guisection = '' # works with python 2.0, but is not SAX compliant @@ -283,6 +309,8 @@ self.my_characters(ch) def my_characters(self, ch): + if self.inLabelContent: + self.label = self.label + ch if self.inDescriptionContent: self.description = self.description + ch if self.inDefaultContent: @@ -308,12 +336,18 @@ def endElement(self, name): # If it's not a parameter element, ignore it if name == 'parameter': - self.inParameter = 0; + self.inParameter = False; + # description -> label + if not self.param_label: + self.param_label = self.param_description + self.param_description = '' + self.task.params.append({ "name" : self.param_name, "type" : self.param_type, "required" : self.param_required, "multiple" : self.param_multiple, + "label" : self.param_label, "description" : self.param_description, 'gisprompt' : self.param_gisprompt, 'age' : self.param_age, @@ -325,11 +359,14 @@ "value" : '' }) if name == 'flag': - self.inFlag = 0; + self.inFlag = False; self.task.flags.append({ "name" : self.flag_name, "description" : self.flag_description } ) + if name == 'label': + self.param_label = normalize_whitespace(self.label) + if name == 'description': if self.inParameter: self.param_description = normalize_whitespace(self.description) @@ -337,32 +374,34 @@ self.flag_description = normalize_whitespace(self.description) else: self.task.description = normalize_whitespace(self.description) - self.inDescriptionContent = 0 + self.inDescriptionContent = False if name == 'default': self.param_default = normalize_whitespace(self.param_default) - self.inDefaultContent = 0 + self.inDefaultContent = False if name == 'value': v = normalize_whitespace(self.value_tmp) self.param_values = self.param_values + [ normalize_whitespace(self.value_tmp) ] - self.inValueContent = 0 + self.inValueContent = False if name == 'guisection': self.param_guisection = normalize_whitespace(self.param_guisection) - self.inGuisection = 0 + self.inGuisection = False class helpPanel(wx.html.HtmlWindow): - """This panel holds the text from GRASS docs. + """ + This panel holds the text from GRASS docs. GISBASE must be set in the environment to find the html docs dir. The SYNOPSIS section is skipped, since this Panel is supposed to - be integrated into the cmdPanel.""" + be integrated into the cmdPanel. + """ def __init__(self, grass_command = "index", *args, **kwargs): wx.html.HtmlWindow.__init__(self, *args, **kwargs) self.fspath = os.getenv( "GISBASE" ) + "/docs/html/" - self.SetStandardFonts( size = 11 ) - self.fillContentsFromFile( self.fspath + grass_command + ".html" ) + self.SetStandardFonts ( size = 10 ) + self.fillContentsFromFile ( self.fspath + grass_command + ".html" ) def fillContentsFromFile( self, htmlFile ): aLink = re.compile( r'()', re.IGNORECASE ) @@ -386,57 +425,77 @@ class mainFrame(wx.Frame): - """This is the Frame containing the dialog for options input. + """ + This is the Frame containing the dialog for options input. The dialog is organized in a notebook according to the guisections defined by each GRASS command. If run with a parent, it may Apply, Ok or Cancel; the latter two close the dialog. The former two trigger a callback. - + If run standalone, it will allow execution of the command. - The command is checked and sent to the clipboard when clicking "Copy". """ + The command is checked and sent to the clipboard when clicking 'Copy'. + """ def __init__(self, parent, ID, task_description, get_dcmd=None, layer=None): self.get_dcmd = get_dcmd self.layer = layer self.task = task_description + self.parent = parent - wx.Frame.__init__(self, parent, ID, self.task.name, - wx.DefaultPosition, style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL) + standalone = True + + wx.Frame.__init__(self, parent=parent, id=ID, title=self.task.name, + pos=wx.DefaultPosition, style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL) + # statusbar self.CreateStatusBar() - self.parent = parent - self.SetIcon(wx.Icon(os.path.join(imagepath,'grass.form.gif'), wx.BITMAP_TYPE_ANY)) + # icon + self.SetIcon(wx.Icon(os.path.join(imagepath, 'grass.form.gif'), wx.BITMAP_TYPE_ANY)) - menu = wx.Menu() - menu.Append(wx.ID_ABOUT, _("&About GrassGUI"), - _("Information about GrassGUI") ) - menu.Append(ID_ABOUT_COMMAND, _("&About %s") % self.task.name, - _("Short descripton of GRASS command %s") % self.task.name) - menu.AppendSeparator() - menu.Append(wx.ID_EXIT, _("E&xit"), _("Terminate the program") ) - - menuBar = wx.MenuBar() - menuBar.Append(menu, "&File"); - - self.SetMenuBar(menuBar) + # menu + # menu = wx.Menu() + # menu.Append(wx.ID_ABOUT, _("&About GrassGUI"), + # _("Information about GrassGUI") ) + # menu.Append(ID_ABOUT_COMMAND, _("&About %s") % self.task.name, + # _("Short descripton of GRASS command %s") % self.task.name) + # menu.AppendSeparator() + # menu.Append(wx.ID_EXIT, _("E&xit"), _("Terminate the program") ) + # menuBar = wx.MenuBar() + # menuBar.Append(menu, "&File"); + # self.SetMenuBar(menuBar) + #wx.EVT_MENU(self, wx.ID_ABOUT, self.OnAbout) + #wx.EVT_MENU(self, ID_ABOUT_COMMAND, self.OnAboutCommand) + #wx.EVT_MENU(self, wx.ID_EXIT, self.OnCancel) + guisizer = wx.BoxSizer(wx.VERTICAL) # set apropriate output window -# if self.parent: -# standalone=False -# self.goutput = self.parent.goutput -# else: - standalone=True - self.notebookpanel = cmdPanel( parent=self, task=self.task, standalone=standalone ) + # if self.parent: + # standalone=False + # self.goutput = self.parent.goutput + # else: + + # logo+description + topsizer = wx.BoxSizer(wx.HORIZONTAL) + # GRASS logo + self.logo = wx.StaticBitmap (parent=self, bitmap=wx.Bitmap(name=os.path.join(imagepath, 'grass-tiny-logo.png'), type=wx.BITMAP_TYPE_ANY)) + topsizer.Add (item=self.logo, proportion=0, border=1, flag=wx.ALL) + # module description + self.description = wx.StaticText (parent=self, label=self.task.description) + topsizer.Add (item=self.description, proportion=0, border=5, flag=wx.ALL) + guisizer.Add (item=topsizer, proportion=0, flag=wx.ALIGN_BOTTOM) + + # notebooks + self.notebookpanel = cmdPanel (parent=self, task=self.task, standalone=standalone) if standalone: self.goutput = self.notebookpanel.goutput self.notebookpanel.OnUpdateValues = self.updateValuesHook + guisizer.Add (item=self.notebookpanel, proportion=1, flag=wx.EXPAND) - guisizer.Add( self.notebookpanel, 1, flag = wx.EXPAND ) - + # status bar status_text = _("Enter parameters for ") + self.task.name if self.notebookpanel.hasMain: # We have to wait for the notebookpanel to be filled in order @@ -444,9 +503,12 @@ status_text += _(" (those of Main in bold typeface are required)") self.SetStatusText( status_text ) - btnsizer = wx.BoxSizer(wx.HORIZONTAL) - btn_cancel = wx.Button(self, wx.ID_CANCEL, _("Cancel") ) - btnsizer.Add( btn_cancel, 0, wx.ALL| wx.ALIGN_CENTER, 10) + # buttons + btnsizer = wx.BoxSizer(orient=wx.HORIZONTAL) + # cancel + btn_cancel = wx.Button(parent=self, id=wx.ID_CANCEL) + btn_cancel.SetToolTipString(_("Cancel the command")) + btnsizer.Add(item=btn_cancel, proportion=0, flag=wx.ALL | wx.ALIGN_CENTER, border=10) if self.get_dcmd is not None: # A callback has been set up btn_apply = wx.Button(self, wx.ID_APPLY, _("Apply") ) btnsizer.Add( btn_apply, 0, wx.ALL| wx.ALIGN_CENTER, 10) @@ -456,17 +518,18 @@ btn_apply.Bind(wx.EVT_BUTTON, self.OnApply) btn_ok.Bind(wx.EVT_BUTTON, self.OnOK) else: # We're standalone - btn_run = wx.Button(self, wx.ID_OK, _("Run") ) - btnsizer.Add( btn_run, 0, wx.ALL| wx.ALIGN_CENTER, 10) + # run + btn_run = wx.Button(parent=self, id=wx.ID_OK, label= _("&Run")) + btn_run.SetToolTipString(_("Run the command")) btn_run.SetDefault() btn_run.Bind(wx.EVT_BUTTON, self.OnRun) - btn_clipboard = wx.Button(self, wx.ID_OK, _("Copy") ) - btnsizer.Add(btn_clipboard, 0, wx.ALL| wx.ALIGN_CENTER, 10) + btnsizer.Add( item=btn_run, proportion=0, flag=wx.ALL| wx.ALIGN_CENTER, border=10) + # copy + btn_clipboard = wx.Button(parent=self, id=wx.ID_OK, label=_("C&opy") ) + btn_clipboard.SetToolTipString(_("Copy the command to clipboard")) btn_clipboard.Bind(wx.EVT_BUTTON, self.OnCopy) - guisizer.Add(btnsizer, 0, wx.ALIGN_BOTTOM) - wx.EVT_MENU(self, wx.ID_ABOUT, self.OnAbout) - wx.EVT_MENU(self, ID_ABOUT_COMMAND, self.OnAboutCommand) - wx.EVT_MENU(self, wx.ID_EXIT, self.OnCancel) + btnsizer.Add(item=btn_clipboard, proportion=0, flag=wx.ALL| wx.ALIGN_CENTER, border=10) + guisizer.Add(item=btnsizer, proportion=0, flag=wx.ALIGN_CENTER) btn_cancel.Bind(wx.EVT_BUTTON, self.OnCancel) self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) @@ -500,7 +563,14 @@ def OnRun(self, event): cmd = self.createCmd() - if cmd != None and cmd[0:2] != "d.": + if cmd == None: + return + + # change page if needed + if self.notebookpanel.notebook.GetSelection() != self.notebookpanel.outpageid: + self.notebookpanel.notebook.SetSelection(self.notebookpanel.outpageid) + + if cmd[0:2] != "d.": # Send any non-display command to parent window (probably wxgui.py) # put to parents try: @@ -556,16 +626,20 @@ class cmdPanel(wx.Panel): - """A panel containing a notebook dividing in tabs the different guisections of the GRASS cmd.""" + """ + A panel containing a notebook dividing in tabs the different guisections of the GRASS cmd. + """ def __init__( self, parent, task, standalone, *args, **kwargs ): wx.Panel.__init__( self, parent, *args, **kwargs ) self.task = task - + fontsize = 10 + # Determine tab layout sections = [] is_section = {} not_hidden = [ p for p in self.task.params + self.task.flags if not p.get( 'hidden','no' ) == 'yes' ] + for task in not_hidden: if task.get( 'required','no' ) == 'yes': # All required go into Main, even if they had defined another guisection @@ -579,6 +653,7 @@ sections.append( task['guisection'] ) else: is_section[ task['guisection'] ] += 1 + # Main goes first, Options goes second for (newidx,content) in [ (0,_( 'Main' )), (1,_('Options')) ]: if content in sections: @@ -586,85 +661,118 @@ sections[idx:idx+1] = [] sections[newidx:newidx] = [content] - panelsizer = wx.BoxSizer(wx.VERTICAL) + panelsizer = wx.BoxSizer(orient=wx.VERTICAL) + # Build notebook nbStyle=FN.FNB_NO_X_BUTTON|FN.FNB_VC8|FN.FNB_BACKGROUND_GRADIENT - notebook = FN.FlatNotebook( self, id=wx.ID_ANY, style=nbStyle) - notebook.SetTabAreaColour(wx.Colour(125,200,175)) - notebook.Bind( FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnPageChange ) + self.notebook = FN.FlatNotebook( self, id=wx.ID_ANY, style=nbStyle) + self.notebook.SetTabAreaColour(wx.Colour(125,200,175)) + self.notebook.Bind( FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnPageChange ) tab = {} tabsizer = {} for section in sections: - tab[section] = wx.ScrolledWindow( notebook ) + tab[section] = wx.ScrolledWindow( parent=self.notebook ) tab[section].SetScrollRate(10,10) - tabsizer[section] = wx.BoxSizer(wx.VERTICAL) - notebook.AddPage( tab[section], text = section ) + tabsizer[section] = wx.BoxSizer(orient=wx.VERTICAL) + self.notebook.AddPage( tab[section], text=section ) # are we running from command line? if standalone: from gui_modules import wxgui_utils self.goutput = wxgui_utils.GMConsole(self) - self.outpage = notebook.AddPage(self.goutput, text=_("Command output") ) + self.outpage = self.notebook.AddPage(self.goutput, text=_("Command output") ) + self.outpageid = self.notebook.GetPageCount() - 1 - manual_tab = helpPanel( parent = notebook, grass_command = self.task.name) + manual_tab = helpPanel( parent = self.notebook, grass_command = self.task.name) if manual_tab.Ok: manual_tabsizer = wx.BoxSizer(wx.VERTICAL) - notebook.AddPage( manual_tab, text = _("Manual") ) + self.notebook.AddPage( manual_tab, text = _("Manual") ) - notebook.SetSelection(0) - panelsizer.Add( notebook, 1, flag=wx.EXPAND ) + self.notebook.SetSelection(0) + panelsizer.Add( self.notebook, 1, flag=wx.EXPAND ) + # flags + text_style = wx.FONTWEIGHT_NORMAL + visible_flags = [ f for f in self.task.flags if not f.get( 'hidden', 'no' ) == 'yes' ] + for f in visible_flags: + which_sizer = tabsizer[ f['guisection'] ] + which_panel = tab[ f['guisection'] ] + title = text_beautify( f['description'] ) + chk = wx.CheckBox(parent=which_panel, label = title, style = wx.NO_BORDER) + if 'value' in f: + chk.SetValue( f['value'] ) + chk.SetFont( wx.Font( pointSize=fontsize, family=wx.FONTFAMILY_DEFAULT, style=wx.NORMAL, weight=text_style)) + which_sizer.Add( item=chk, proportion=0, flag=wx.EXPAND| wx.TOP | wx.LEFT, border=5) + f['wxId'] = chk.GetId() + chk.Bind(wx.EVT_CHECKBOX, self.OnSetValue) + + # parameters visible_params = [ p for p in self.task.params if not p.get( 'hidden', 'no' ) == 'yes' ] for p in visible_params: which_sizer = tabsizer[ p['guisection'] ] which_panel = tab[ p['guisection'] ] - title = text_beautify( p['description'] ) - text_style = wx.FONTWEIGHT_BOLD + title = text_beautify( p['label'] ) + tooltip = text_beautify (p['description']) txt = None + + # text style (required -> bold) if p.get('required','no') == 'no': text_style = wx.FONTWEIGHT_NORMAL + else: + text_style = wx.FONTWEIGHT_BOLD + + # title expansion if p.get('multiple','no') == 'yes' and len( p.get('values','') ) == 0: title = _("[multiple]") + " " + title - if p.get('value','') == '' : - p['value'] = p.get('default','') + if p.get('value','') == '' : + p['value'] = p.get('default','') + if ( len(p.get('values',[]) ) > 0): - valuelist=map(str,p.get('values',[])) + # list of values if p.get('multiple','no') == 'yes': - txt = wx.StaticBox(which_panel,0,title+":") - hSizer=wx.StaticBoxSizer( txt, wx.VERTICAL ) + txt = wx.StaticBox (parent=which_panel, id=0, label=" " + title + ": ") + txt.SetToolTip(wx.ToolTip(tooltip)) + if len(valuelist) > 6: + hSizer=wx.StaticBoxSizer ( box=txt, orient=wx.VERTICAL ) + else: + hSizer=wx.StaticBoxSizer ( box=txt, orient=wx.HORIZONTAL ) isDefault = {} for defval in p['value'].split(','): isDefault[ defval ] = 'yes' - # for multi checkboxes, this is an array of all wx IDs - # for each individual checkbox - p[ 'wxId' ]=[] + # for multi checkboxes, this is an array of all wx IDs + # for each individual checkbox + p[ 'wxId' ]=[] for val in valuelist: - chkbox = wx.CheckBox( which_panel, label = text_beautify(val) ) + chkbox = wx.CheckBox( parent=which_panel, label = text_beautify(val) ) p[ 'wxId' ].append( chkbox.GetId() ) if isDefault.has_key(val): chkbox.SetValue( True ) - hSizer.Add( chkbox,0,wx.ADJUST_MINSIZE,5 ) + hSizer.Add( item=chkbox, proportion=0, flag=wx.ADJUST_MINSIZE | wx.ALL, border=1 ) chkbox.Bind(wx.EVT_CHECKBOX, self.OnCheckBoxMulti) - which_sizer.Add( hSizer, 0, wx.ADJUST_MINSIZE, 5) + which_sizer.Add( item=hSizer, proportion=0, flag=wx.ADJUST_MINSIZE | wx.ALL, border=5 ) + # one value elif len(valuelist) == 1: - txt = wx.StaticText(which_panel, - label = _('%s. Valid range=%s') % (title, str(valuelist).strip("[]'") + ':' ) ) - which_sizer.Add(txt, 0, wx.ADJUST_MINSIZE | wx.ALL, 5) - txt2 = wx.TextCtrl(which_panel, value = p.get('default',''), - size = (STRING_ENTRY_WIDTH, ENTRY_HEIGHT)) - if p.get('value','') != '': txt2.SetValue(p['value']) # parameter previously set - - which_sizer.Add( txt2, 0, wx.ADJUST_MINSIZE, 5) + txt = wx.StaticText(parent=which_panel, + label = _('%s. Valid range=%s') % (title, str(valuelist).strip("[]'") + ':' ) ) + which_sizer.Add(item=txt, proportion=0, flag=wx.ADJUST_MINSIZE | wx.TOP | wx.LEFT, border=5) + + txt2 = wx.TextCtrl(parent=which_panel, value = p.get('default',''), + size = (STRING_ENTRY_WIDTH, ENTRY_HEIGHT)) + if p.get('value','') != '': + txt2.SetValue(p['value']) # parameter previously set + which_sizer.Add(item=txt2, proportion=0, flag=wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT, border=5) + p['wxId'] = txt2.GetId() txt2.Bind(wx.EVT_TEXT, self.OnSetValue) else: - txt = wx.StaticText(which_panel, label = title + ':' ) - which_sizer.Add(txt, 0, wx.ADJUST_MINSIZE | wx.ALL, 5) + # combo + txt = wx.StaticText(parent=which_panel, label = title + ':' ) + which_sizer.Add(item=txt, proportion=0, flag=wx.ADJUST_MINSIZE | wx.TOP | wx.LEFT, border=5) cb = wx.ComboBox(which_panel, -1, p.get('default',''), wx.Point(-1, -1), wx.Size(STRING_ENTRY_WIDTH, -1), valuelist, wx.CB_DROPDOWN) if p.get('value','') != '': cb.SetValue(p['value']) # parameter previously set - which_sizer.Add( cb, 0, wx.ADJUST_MINSIZE, 5) + which_sizer.Add( item=cb, proportion=0, flag=wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT, border=5) p['wxId'] = cb.GetId() cb.Bind( wx.EVT_COMBOBOX, self.OnSetValue) @@ -674,25 +782,29 @@ and p.get('gisprompt',False) == False and p.get('prompt','') != 'color'): - txt = wx.StaticText(which_panel, label = title + ':' ) - which_sizer.Add(txt, 0, wx.ADJUST_MINSIZE | wx.ALL, 5) + txt = wx.StaticText(parent=which_panel, label = title + ':' ) + which_sizer.Add(item=txt, proportion=0, flag=wx.TOP | wx.LEFT | wx.EXPAND, border=5) - txt3 = wx.TextCtrl(which_panel, value = p.get('default',''), - size = (STRING_ENTRY_WIDTH, ENTRY_HEIGHT)) - if p.get('value','') != '': txt3.SetValue(p['value']) # parameter previously set - which_sizer.Add( txt3, 0, wx.ADJUST_MINSIZE| wx.ALL, 5) + txt3 = wx.TextCtrl(parent=which_panel, value = p.get('default',''), + size = (STRING_ENTRY_WIDTH, ENTRY_HEIGHT)) + + if p.get('value','') != '': + txt3.SetValue(p['value']) # parameter previously set + + which_sizer.Add(item=txt3, proportion=0, flag=wx.BOTTOM | wx.LEFT, border=5) p['wxId'] = txt3.GetId() txt3.Bind(wx.EVT_TEXT, self.OnSetValue) if p.get('type','string') == 'string' and p.get('gisprompt',False) == True: - txt = wx.StaticText(which_panel, label = title + ':') - which_sizer.Add(txt, 0, wx.ADJUST_MINSIZE | wx.ALL, 5) + txt = wx.StaticText(parent=which_panel, label = title + ':') + which_sizer.Add(item=txt, proportion=0, flag=wx.ADJUST_MINSIZE | wx.TOP | wx.LEFT, border=5) # element selection tree combobox (maps, icons, regions, etc.) if p.get('prompt','') != 'color': - selection = select.Select(which_panel, id=wx.ID_ANY, size=(300,-1), - type=p.get('element','') ) - if p.get('value','') != '': selection.SetValue(p['value']) # parameter previously set - which_sizer.Add( selection, 0, wx.ADJUST_MINSIZE| wx.ALL, 5) + selection = select.Select(parent=which_panel, id=wx.ID_ANY, size=(300,-1), + type=p.get('element','') ) + if p.get('value','') != '': + selection.SetValue(p['value']) # parameter previously set + which_sizer.Add(item=selection, proportion=0, flag=wx.ADJUST_MINSIZE| wx.BOTTOM | wx.LEFT, border=5) # A select.Select is a combobox with two children: a textctl and a popupwindow; # we target the textctl here p['wxId'] = selection.GetChildren()[0].GetId() @@ -706,11 +818,11 @@ if p.get('value','') != '': # parameter previously set default_color, label_color = color_resolve( p['value'] ) if "none" in title: - this_sizer = wx.BoxSizer( wx.HORIZONTAL ) + this_sizer = wx.BoxSizer(orient=wx.HORIZONTAL ) else: this_sizer = which_sizer btn_colour = csel.ColourSelect(which_panel, wx.ID_ANY, label_color, default_color, wx.DefaultPosition, (150,-1) ) - this_sizer.Add(btn_colour, 0, wx.ADJUST_MINSIZE| wx.ALL, 5) + this_sizer.Add(item=btn_colour, proportion=0, flag=wx.ADJUST_MINSIZE | wx.BOTTOM | wx.LEFT, border=5) # For color selectors, this is a two-member array, holding the IDs of # the selector proper and either a "transparent" button or None p['wxId'] = [btn_colour.GetId(),] @@ -721,7 +833,7 @@ none_check.SetValue(True) else: none_check.SetValue(False) - none_check.SetFont( wx.Font( 12, wx.FONTFAMILY_DEFAULT, wx.NORMAL, text_style, 0, '')) + none_check.SetFont( wx.Font( fontsize, wx.FONTFAMILY_DEFAULT, wx.NORMAL, text_style, 0, '')) this_sizer.Add(none_check, 0, wx.ADJUST_MINSIZE| wx.ALL, 5) which_sizer.Add( this_sizer ) none_check.Bind(wx.EVT_CHECKBOX, self.OnColorChange) @@ -729,20 +841,8 @@ else: p['wxId'].append(None) if txt is not None: - txt.SetFont( wx.Font( 12, wx.FONTFAMILY_DEFAULT, wx.NORMAL, text_style, 0, '')) + txt.SetFont( wx.Font( fontsize, wx.FONTFAMILY_DEFAULT, wx.NORMAL, text_style, 0, '')) - visible_flags = [ f for f in self.task.flags if not f.get( 'hidden', 'no' ) == 'yes' ] - for f in visible_flags: - which_sizer = tabsizer[ f['guisection'] ] - which_panel = tab[ f['guisection'] ] - title = text_beautify( f['description'] ) - chk = wx.CheckBox(which_panel,-1, label = title, style = wx.NO_BORDER) - if 'value' in f: chk.SetValue( f['value'] ) - chk.SetFont( wx.Font( 12, wx.FONTFAMILY_DEFAULT, wx.NORMAL, text_style, 0, '')) - which_sizer.Add( chk, 0, wx.EXPAND| wx.ALL, 5) - f['wxId'] = chk.GetId() - chk.Bind(wx.EVT_CHECKBOX, self.OnSetValue) - maxsizes = (0,0) for section in sections: tabsizer[section].SetSizeHints( tab[section] ) @@ -763,7 +863,6 @@ self.SetSizer( panelsizer ) self.hasMain = tab.has_key( _('Main') ) # publish, to enclosing Frame for instance - def OnPageChange(self, event): self.Layout() @@ -828,7 +927,8 @@ def createCmd( self, ignoreErrors = False ): - """Produce a command line string for feeding into GRASS. + """ + Produce a command line string for feeding into GRASS. If ignoreErrors==True then it will return whatever has been built so far, even though it would not be a correct command @@ -839,15 +939,18 @@ dlg = wx.MessageDialog(self, str(err), _("Error"), wx.OK | wx.ICON_ERROR) dlg.ShowModal() dlg.Destroy() - cmd = '' + cmd = None + return cmd def getInterfaceDescription( cmd ): - """Returns the XML description for the GRASS cmd. + """ + Returns the XML description for the GRASS cmd. The DTD must be located in $GISBASE/etx/wx/gui_modules/grass-interface.dtd, - otherwise the parser will not succeed.""" + otherwise the parser will not succeed. + """ gmpath = os.getenv("GISBASE") + "/etc/wx/gui_modules" cmdout = os.popen(cmd + r' --interface-description', "r").read() if not len(cmdout) > 0 : @@ -858,21 +961,25 @@ return cmdout class GrassGUIApp(wx.App): - """Stand-alone GRASS command GUI""" + """ + Stand-alone GRASS command GUI + """ def __init__(self, grass_task): self.grass_task = grass_task wx.App.__init__(self) def OnInit(self): - self.mf = mainFrame(None ,-1, self.grass_task ) + self.mf = mainFrame(parent=None, ID=wx.ID_ANY, task_description=self.grass_task) self.mf.Show(True) self.SetTopWindow(self.mf) return True class GUI: def __init__(self, parent=-1): - '''Parses GRASS commands when module is imported and used - from wxgui.py''' + """ + Parses GRASS commands when module is imported and used + from wxgui.py + """ self.parent = parent def parseCommand(self, cmd, gmpath, completed=None, parentframe=-1 ): @@ -921,9 +1028,9 @@ task = grassTask( "d.vect" ) task.get_param('map')['value'] = "map_name" task.get_flag('v')['value'] = True - task.get_param('layer')['value'] = 12 + task.get_param('layer')['value'] = 1 task.get_param('bcolor')['value'] = "red" - assert ' '.join( task.getCmd() ) == "d.vect -v map=map_name layer=12 bcolor=red" + assert ' '.join( task.getCmd() ) == "d.vect -v map=map_name layer=1 bcolor=red" # Test interface building with handmade grassTask task = grassTask() task.name = "TestTask" Modified: trunk/grassaddons/gui/gui_modules/render.py =================================================================== --- trunk/grassaddons/gui/gui_modules/render.py 2007-04-18 13:37:32 UTC (rev 527) +++ trunk/grassaddons/gui/gui_modules/render.py 2007-04-19 15:10:15 UTC (rev 528) @@ -131,7 +131,7 @@ except StandardError, e: sys.stderr.write("Could not render command layer <%s>: %s\n" %\ - (self.name, str(e))) + (self.name, str(e))) self.cmd = None def __renderOverlay(self): Modified: trunk/grassaddons/gui/gui_modules/wxgui_utils.py =================================================================== --- trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-18 13:37:32 UTC (rev 527) +++ trunk/grassaddons/gui/gui_modules/wxgui_utils.py 2007-04-19 15:10:15 UTC (rev 528) @@ -9,16 +9,15 @@ import menuform import mapdisp import render +import cmd +from debug import Debug as Debug - - -#FIXME?? try: from subprocess import * except: from compat import subprocess - from compat.subprocess import * + gmpath = os.getenv("GISBASE") + "/etc/wx/gui_modules/" sys.path.append(gmpath) @@ -171,8 +170,10 @@ self.first = True params = {} # no initial options parameters - if self.layer_selected: self.SelectItem(self.layer_selected, select=False) + if self.layer_selected: + self.SelectItem(self.layer_selected, select=False) + Debug.msg (3, "LayerTree().AddLayer(): type=%s" % (type)) if type == 'command': # generic command layer self.ctrl = wx.TextCtrl(self, id=wx.ID_ANY, value='', @@ -756,7 +757,7 @@ self.gcmdlst = self.gcmdlst + os.listdir(gisbase+r'/scripts') return self.gcmdlst - def runCmd(self, cmd): + def runCmd(self, command): """ Run in GUI or shell GRASS (or other) commands typed into console command text widget, echo command to @@ -771,38 +772,38 @@ gcmdlst = self.getGRASSCmds() cmdlst = [] -# cmd = self.console_command.GetLineText(0) - cmdlst = cmd.split(' ') + # cmd = self.console_command.GetLineText(0) + cmdlst = command.split(' ') try: -# disp_idx = int(track.Track().GetDisp()[0]) -# curr_disp = track.Track().GetDisp()[1] - curr_disp = self.Parent.Parent.curr_page.maptree.mapdisplay + # disp_idx = int(track.Track().GetDisp()[0]) + # curr_disp = track.Track().GetDisp()[1] + curr_disp = self.Parent.Parent.curr_page.maptree.mapdisplay except: -# disp_idx = None + # disp_idx = None curr_disp = None - if len(cmdlst) == 1 and cmd in gcmdlst: - # Send GRASS command without arguments to GUI command interface - # except display commands (they are handled differently) + if len(cmdlst) == 1 and command in gcmdlst: + # Send GRASS command without arguments to GUI command interface + # except display commands (they are handled differently) global gmpath - if cmd[0:2] == "d.": - if cmd == 'd.rast': + if command[0:2] == "d.": + if command == 'd.rast': layertype = 'raster' - elif cmd == 'd.rgb': + elif command == 'd.rgb': layertype = 'rgb' - elif cmd == 'd.his': + elif command == 'd.his': layertype = 'his' - elif cmd == 'd.legend': + elif command == 'd.legend': layertype = 'rastleg' - elif cmd == 'd.vect': + elif command == 'd.vect': layertype = 'vector' - elif cmd == 'd.vect.thematic': + elif command == 'd.vect.thematic': layertype = 'thememap' - elif cmd == 'd.vect.chart': + elif command == 'd.vect.chart': layertype = 'themechart' - elif cmd == 'd.grid': + elif command == 'd.grid': layertype = 'grid' - elif cmd == 'd.labels': + elif command == 'd.labels': layertype = 'labels' else: print 'Command type not yet implemented' @@ -815,45 +816,45 @@ self.Parent.Parent.curr_page.maptree.AddLayer(layertype) else: - menuform.GUI().parseCommand(cmd, gmpath, parentframe=None) - self.cmd_output.write(cmdlst[0] + + menuform.GUI().parseCommand(command, gmpath, parentframe=None) + self.command_output.write(cmdlst[0] + "\n----------\n") - elif cmd[0:2] == "d." and len(cmdlst) > 1 and cmdlst[0] in gcmdlst: + elif command[0:2] == "d." and len(cmdlst) > 1 and cmdlst[0] in gcmdlst: """ Send GRASS display command(s)with arguments to the display processor and echo to command output console. Accepts a list of d.* commands separated by commas. Display with focus receives display command(s). """ - self.cmd_output.write(cmd + - "\n----------\n") - dcmds = cmd.split(',') + #self.cmd_output.write(command + "\n----------\n") + self.cmd_output.write("$" + command) + dcmds = command.split(',') curr_disp.addMapsToList(type='command', map=dcmds, mset=None) curr_disp.ReDrawCommand() else: - # Send any other command to the shell. Send output to - # console output window. + # Send any other command to the shell. Send output to + # console output window. try: os.environ["GRASS_MESSAGE_FORMAT"] = "gui" - self.cmd_output.write(cmd+"\n----------\n") - + #self.cmd_output.write(command+"\n----------\n") + self.cmd_output.write("$ " + command + "\n") # activate compuational region (set with g.region) for all non-display commands. tmpreg = os.getenv("GRASS_REGION") os.unsetenv("GRASS_REGION") - p = Popen(cmd +" --verbose", shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) + p = cmd.Command(cmd=command + " --verbose") # deactivate computational region and return to display settings if tmpreg: os.environ["GRASS_REGION"] = tmpreg - oline = p.stderr.readline() + oline = p.module_stderr.readline() while oline: oline = oline.strip() - oline = p.stderr.readline() + oline = p.module_stderr.readline() # make some progress #GRASS_INFO_PERCENT: 100 if oline.find("GRASS_INFO_PERCENT")>-1: @@ -866,10 +867,10 @@ self.cmd_output.write("ERROR: "+string.split(oline,maxsplit=1)[1]+"\n") - oline = p.stdout.readline() + oline = p.module_stdout.readline() while oline: oline = oline.strip() - if cmd.split(' ')[0] == 'r.what': + if command.split(' ')[0] == 'r.what': rastqlist = oline.split('|') self.cmd_output.write('East: '+rastqlist[0]+"\n") self.cmd_output.write('North: '+rastqlist[1]+"\n") @@ -882,20 +883,20 @@ else: self.cmd_output.write(oline+"\n") print >> sys.stderr, oline - oline = p.stdout.readline() - self.cmd_output.write("\n==========\n") - if p.stdout < 0: - print >> sys.stderr, "Child was terminated by signal", p.stdout - elif p.stdout > 0: - #print >> sys.stderr, p.stdout - pass + oline = p.module_stdout.readline() + #self.cmd_output.write("\n==========\n") + if p.module_stdout < 0: + print >> sys.stderr, "Child was terminated by signal", p.module_stdout + elif p.module_stdout > 0: + #print >> sys.stderr, p.module_stdout + pass except OSError, e: - print >> sys.stderr, "Execution failed:", e + print >> sys.stderr, "Execution failed:", e def clearHistory(self, event): - self.cmd_output.Clear() - self.console_progressbar.SetValue(0) + self.cmd_output.Clear() + self.console_progressbar.SetValue(0) def saveHistory(self, event): self.history = self.cmd_output.GetStringSelection() From calvelo at grass.itc.it Thu Apr 19 20:11:42 2007 From: calvelo at grass.itc.it (calvelo@grass.itc.it) Date: Thu Apr 19 20:11:44 2007 Subject: [grass-addons] r529 - trunk/grassaddons/gui/gui_modules Message-ID: <200704191811.l3JIBgvX012341@grass.itc.it> Author: calvelo Date: 2007-04-19 20:11:35 +0200 (Thu, 19 Apr 2007) New Revision: 529 Modified: trunk/grassaddons/gui/gui_modules/menuform.py Log: - moved label<->description replacement deeper into GUI, outside of the parser - added more test cases - cleaned color converter - cosmetics Modified: trunk/grassaddons/gui/gui_modules/menuform.py =================================================================== --- trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-19 15:10:15 UTC (rev 528) +++ trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-19 18:11:35 UTC (rev 529) @@ -15,7 +15,7 @@ # Author: Jan-Oliver Wagner # improved by: Bernhard Reiter # Improved by: Michael Barton, Arizona State University -# Improved by: Daniel Calvelo +# Improved by: Daniel Calvelo # # This program is free software under the GPL (>=v2) # Read the file COPYING coming with GRASS for details. @@ -24,7 +24,7 @@ # automatically build a GUI from a xml-based # GRASS user interface description. # -# You need to have Python 2.4, wx.Python 2.6 and python-xml. +# You need to have Python 2.4, wxPython 2.8 and python-xml. # # The XML stream is read from executing the command given in the # command line, thus you may call it for instance this way: @@ -34,7 +34,8 @@ # Or you set an alias or wrap the call up in a nice # shell script, GUI environment ... please contribute your idea. # -# Updated to wxPython 2.6 syntax. Methods added to make it callable by gui. +# Updated to wxPython 2.8 syntax and contrib widgets. +# Methods added to make it callable by gui. # Method added to automatically re-run with pythonw on a Mac. # # TODO: @@ -95,34 +96,27 @@ BUTTON_HEIGHT = 44 BUTTON_WIDTH = 100 -t_colors = "red,orange,yellow,green,blue,indigo,violet,white,black,gray,brown,magenta,aqua,grey,cyan,purple" -t_rgb = ( # From lib/gis/col_str.c - (255, 0, 0), - (255,128, 0), - (255,255, 0), - ( 0,255, 0), - ( 0, 0,255), - ( 0,128,255), - (128, 0,255), - (255,255,255), - ( 0, 0, 0), - (128,128,128), - (180, 77, 25), - (255, 0,255), - (100,128,255), - (128,128,128), - ( 0,255,255), - (128, 0,128) -) -t_color = t_colors.split(',') -str2rgb = {} +# From lib/gis/col_str.c, except purple which is mentioned +# there but not given RGB values +str2rgb = {'aqua': (100, 128, 255), + 'black': (0, 0, 0), + 'blue': (0, 0, 255), + 'brown': (180, 77, 25), + 'cyan': (0, 255, 255), + 'gray': (128, 128, 128), + 'green': (0, 255, 0), + 'grey': (128, 128, 128), + 'indigo': (0, 128, 255), + 'magenta': (255, 0, 255), + 'orange': (255, 128, 0), + 'purple': (128, 0, 128), + 'red': (255, 0, 0), + 'violet': (128, 0, 255), + 'white': (255, 255, 255), + 'yellow': (255, 255, 0)} rgb2str = {} -for c in range(0,len(t_rgb)): - str2rgb[ t_color[c] ] = t_rgb[ c ] - rgb2str[ t_rgb[ c ] ] = t_color[ c ] -del t_colors -del t_color -del t_rgb +for (s,r) in str2rgb.items(): + rgb2str[ r ] = s def color_resolve(color): if len(color)>0 and color[0] in "0123456789": @@ -224,6 +218,7 @@ errStr += _("Parameter %s (%s) is missing\n") % ( p['name'], p['description'] ) errors += 1 if p.get('value','') != '' and p['value'] != p.get('default','') : + # Output only values that have been set, and different from defaults cmd += [ '%s=%s' % ( p['name'], p['value'] ) ] if errors and not ignoreErrors: raise ValueError, errStr @@ -338,9 +333,9 @@ if name == 'parameter': self.inParameter = False; # description -> label - if not self.param_label: - self.param_label = self.param_description - self.param_description = '' +# if not self.param_label: +# self.param_label = self.param_description +# self.param_description = '' self.task.params.append({ "name" : self.param_name, @@ -395,7 +390,7 @@ GISBASE must be set in the environment to find the html docs dir. The SYNOPSIS section is skipped, since this Panel is supposed to - be integrated into the cmdPanel. + be integrated into the cmdPanel and options are obvious there. """ def __init__(self, grass_command = "index", *args, **kwargs): wx.html.HtmlWindow.__init__(self, *args, **kwargs) @@ -407,14 +402,14 @@ aLink = re.compile( r'()', re.IGNORECASE ) try: contents = [ '' % self.fspath ] - dont_skip = True + skip = False for l in file( htmlFile, "rb" ).readlines(): - if "DESCRIPTION" in l: dont_skip = True - if dont_skip: - if "SYNOPSIS" in l: dont_skip = False # do skip the options description + if "DESCRIPTION" in l: skip = False + if not skip: + if "SYNOPSIS" in l: skip = True # do skip the options description else: findLink = aLink.search( l ) - if findLink is not None: + if findLink is not None: # change URLs to file paths contents.append( aLink.sub(findLink.group(1)+self.fspath+findLink.group(2),l) ) else: contents.append( l ) @@ -507,7 +502,7 @@ btnsizer = wx.BoxSizer(orient=wx.HORIZONTAL) # cancel btn_cancel = wx.Button(parent=self, id=wx.ID_CANCEL) - btn_cancel.SetToolTipString(_("Cancel the command")) + btn_cancel.SetToolTipString(_("Cancel the command settings and ignore changes")) btnsizer.Add(item=btn_cancel, proportion=0, flag=wx.ALL | wx.ALIGN_CENTER, border=10) if self.get_dcmd is not None: # A callback has been set up btn_apply = wx.Button(self, wx.ID_APPLY, _("Apply") ) @@ -711,8 +706,12 @@ for p in visible_params: which_sizer = tabsizer[ p['guisection'] ] which_panel = tab[ p['guisection'] ] - title = text_beautify( p['label'] ) - tooltip = text_beautify (p['description']) + if p.get('label','') != '': + title = text_beautify( p['label'] ) + tooltip = text_beautify ( p['description'] ) + else: + title = text_beautify( p['description'] ) + tooltip = '' txt = None # text style (required -> bold) @@ -728,7 +727,7 @@ p['value'] = p.get('default','') if ( len(p.get('values',[]) ) > 0): - valuelist=map(str,p.get('values',[])) + valuelist=map( str, p.get('values',[]) ) # list of values if p.get('multiple','no') == 'yes': txt = wx.StaticBox (parent=which_panel, id=0, label=" " + title + ": ") @@ -738,7 +737,7 @@ else: hSizer=wx.StaticBoxSizer ( box=txt, orient=wx.HORIZONTAL ) isDefault = {} - for defval in p['value'].split(','): + for defval in p.get('value','').split(','): isDefault[ defval ] = 'yes' # for multi checkboxes, this is an array of all wx IDs # for each individual checkbox @@ -902,7 +901,7 @@ myIndex = p['wxId'].index( me ) # Unpack current value list currentValues={} - for isThere in theParam['value'].split(','): + for isThere in theParam.get('value','').split(','): currentValues[isThere] = 1 theValue = theParam['values'][myIndex] if event.Checked(): @@ -954,7 +953,7 @@ gmpath = os.getenv("GISBASE") + "/etc/wx/gui_modules" cmdout = os.popen(cmd + r' --interface-description', "r").read() if not len(cmdout) > 0 : - raise IOError, "Couldn't make command %s provide its interface description." % cmd + raise IOError, _("Couldn't fetch interface description for command <%s>.") % cmd p = re.compile( '(grass-interface.dtd)') p.search( cmdout ) cmdout = p.sub( gmpath+r'/grass-interface.dtd', cmdout) @@ -1032,13 +1031,15 @@ task.get_param('bcolor')['value'] = "red" assert ' '.join( task.getCmd() ) == "d.vect -v map=map_name layer=1 bcolor=red" # Test interface building with handmade grassTask + # this doesn't need GRASS at all task = grassTask() task.name = "TestTask" - task.description = "This is a artificial grassTask() object intended for testing purposes" + task.description = "This is an artificial grassTask() object intended for testing purposes" task.params = [ { "name" : "text", - "description" : "Enter some text" + "description" : "Descriptions go into tooltips if labels are present, like this one", + "label" : "Enter some text", },{ "name" : "hidden_text", "description" : "This text should not appear in the form", @@ -1078,6 +1079,12 @@ "description" : "A single multiple-choice selection", 'values': ['red', 'green', u'yellow', u'blue', u'purple', u'other'], "guisection" : "tab" + },{ + "name" : "large_multi", + "description" : "A large multiple selection", + "gisprompt" : False, + "multiple" : "yes", + "values" : str2rgb.keys() + map( str, str2rgb.values() ) } ] task.flags = [ From landa at grass.itc.it Fri Apr 20 10:45:25 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Fri Apr 20 10:45:27 2007 Subject: [grass-addons] r530 - trunk/grassaddons/gui/images Message-ID: <200704200845.l3K8jPCU023734@grass.itc.it> Author: landa Date: 2007-04-20 10:45:25 +0200 (Fri, 20 Apr 2007) New Revision: 530 Added: trunk/grassaddons/gui/images/grass-tiny-logo.png Log: added missing file Added: trunk/grassaddons/gui/images/grass-tiny-logo.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gui/images/grass-tiny-logo.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream From landa at grass.itc.it Fri Apr 20 12:01:44 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Fri Apr 20 12:01:45 2007 Subject: [grass-addons] r531 - trunk/grassaddons/gui/gui_modules Message-ID: <200704201001.l3KA1itl024785@grass.itc.it> Author: landa Date: 2007-04-20 12:01:44 +0200 (Fri, 20 Apr 2007) New Revision: 531 Modified: trunk/grassaddons/gui/gui_modules/menuform.py Log: * add keywords ** frame title extension (not so much useful) Modified: trunk/grassaddons/gui/gui_modules/menuform.py =================================================================== --- trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-20 08:45:25 UTC (rev 530) +++ trunk/grassaddons/gui/gui_modules/menuform.py 2007-04-20 10:01:44 UTC (rev 531) @@ -13,9 +13,10 @@ # Copyright (C) 2000-2007 by the GRASS Development Team # Author: Jan-Oliver Wagner -# improved by: Bernhard Reiter +# Improved by: Bernhard Reiter # Improved by: Michael Barton, Arizona State University # Improved by: Daniel Calvelo +# Improved by: Martin Landa # # This program is free software under the GPL (>=v2) # Read the file COPYING coming with GRASS for details. @@ -134,7 +135,7 @@ def normalize_whitespace(text): - "Remove redundant whitespace from a string" + """Remove redundant whitespace from a string""" return string.join( string.split(text), ' ') def text_beautify( someString ): @@ -144,7 +145,7 @@ return escape_ampersand( os.linesep.join( textwrap.wrap( normalize_whitespace(someString), 70 ) ) ) def escape_ampersand(text): - "Escapes ampersands with additional ampersand for GUI" + """Escapes ampersands with additional ampersand for GUI""" return string.replace(text, "&", "&&") class testSAXContentHandler(HandlerBase): @@ -241,13 +242,15 @@ self.inFlag = False self.inGispromptContent = False self.inGuisection = False + self.inKeywordsContent = False self.task = task_description def startElement(self, name, attrs): if name == 'task': self.task.name = attrs.get('name', None) - + self.task.keywords = [] + if name == 'parameter': self.inParameter = True; self.param_label = '' @@ -299,6 +302,10 @@ self.inGuisection = True self.param_guisection = '' + if name == 'keywords': + self.inKeywordsContent = True + self.keyword = '' + # works with python 2.0, but is not SAX compliant def characters(self, ch): self.my_characters(ch) @@ -327,15 +334,17 @@ self.value_tmp = self.value_tmp + ch if self.inGuisection: self.param_guisection = self.param_guisection + ch + if self.inKeywordsContent: + self.keyword = self.keyword + ch def endElement(self, name): # If it's not a parameter element, ignore it if name == 'parameter': self.inParameter = False; # description -> label -# if not self.param_label: -# self.param_label = self.param_description -# self.param_description = '' + # if not self.param_label: + # self.param_label = self.param_description + # self.param_description = '' self.task.params.append({ "name" : self.param_name, @@ -351,7 +360,7 @@ "guisection" : self.param_guisection, "default" : self.param_default, "values" : self.param_values, - "value" : '' }) + "value" : ''}) if name == 'flag': self.inFlag = False; @@ -384,6 +393,11 @@ self.param_guisection = normalize_whitespace(self.param_guisection) self.inGuisection = False + if name == 'keywords': + for keyword in self.keyword.split(','): + self.task.keywords.append (normalize_whitespace(keyword)) + self.inKeywordsContent = False + class helpPanel(wx.html.HtmlWindow): """ This panel holds the text from GRASS docs. @@ -441,8 +455,16 @@ self.parent = parent standalone = True - - wx.Frame.__init__(self, parent=parent, id=ID, title=self.task.name, + + # module name + keywords + title = self.task.name + " [" + for keyId in range(len(self.task.keywords)): + if keyId != 0: + title += ", " + title += self.task.keywords[keyId] + title += "]" + + wx.Frame.__init__(self, parent=parent, id=ID, title=title, pos=wx.DefaultPosition, style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL) # statusbar @@ -706,6 +728,7 @@ for p in visible_params: which_sizer = tabsizer[ p['guisection'] ] which_panel = tab[ p['guisection'] ] + # label <-> description if p.get('label','') != '': title = text_beautify( p['label'] ) tooltip = text_beautify ( p['description'] ) From landa at grass.itc.it Fri Apr 20 12:09:33 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Fri Apr 20 12:09:34 2007 Subject: [grass-addons] r532 - trunk/grassaddons/gui/icons Message-ID: <200704201009.l3KA9XtN024827@grass.itc.it> Author: landa Date: 2007-04-20 12:09:32 +0200 (Fri, 20 Apr 2007) New Revision: 532 Modified: trunk/grassaddons/gui/icons/application.png trunk/grassaddons/gui/icons/application_add.png trunk/grassaddons/gui/icons/application_delete.png trunk/grassaddons/gui/icons/application_lightning.png trunk/grassaddons/gui/icons/chart_bar.png trunk/grassaddons/gui/icons/cog_add.png trunk/grassaddons/gui/icons/cross.png trunk/grassaddons/gui/icons/cursor.png trunk/grassaddons/gui/icons/folder_add.png trunk/grassaddons/gui/icons/grid.png trunk/grassaddons/gui/icons/image_add.png trunk/grassaddons/gui/icons/images.png trunk/grassaddons/gui/icons/information.png trunk/grassaddons/gui/icons/layout_content.png trunk/grassaddons/gui/icons/map.png trunk/grassaddons/gui/icons/map_add.png trunk/grassaddons/gui/icons/map_magnify.png trunk/grassaddons/gui/icons/picture_save.png trunk/grassaddons/gui/icons/printer.png trunk/grassaddons/gui/icons/table_add.png trunk/grassaddons/gui/icons/tag_green.png trunk/grassaddons/gui/icons/zoom.png trunk/grassaddons/gui/icons/zoom_in.png trunk/grassaddons/gui/icons/zoom_out.png Log: propdel svn:executable Property changes on: trunk/grassaddons/gui/icons/application.png ___________________________________________________________________ Name: svn:executable - * Property changes on: trunk/grassaddons/gui/icons/application_add.png ___________________________________________________________________ Name: svn:executable - * Property changes on: trunk/grassaddons/gui/icons/application_delete.png ___________________________________________________________________ Name: svn:executable - * Property changes on: trunk/grassaddons/gui/icons/application_lightning.png ___________________________________________________________________ Name: svn:executable - * Property changes on: trunk/grassaddons/gui/icons/chart_bar.png ___________________________________________________________________ Name: svn:executable - * Property changes on: trunk/grassaddons/gui/icons/cog_add.png ___________________________________________________________________ Name: svn:executable - * Property changes on: trunk/grassaddons/gui/icons/cross.png ___________________________________________________________________ Name: svn:executable - * Property changes on: trunk/grassaddons/gui/icons/cursor.png ___________________________________________________________________ Name: svn:executable - * Property changes on: trunk/grassaddons/gui/icons/folder_add.png ___________________________________________________________________ Name: svn:executable - * Property changes on: trunk/grassaddons/gui/icons/grid.png ___________________________________________________________________ Name: svn:executable - * Property changes on: trunk/grassaddons/gui/icons/image_add.png ___________________________________________________________________ Name: svn:executable - * Property changes on: trunk/grassaddons/gui/icons/images.png ___________________________________________________________________ Name: svn:executable - * Property changes on: trunk/grassaddons/gui/icons/information.png ___________________________________________________________________ Name: svn:executable - * Property changes on: trunk/grassaddons/gui/icons/layout_content.png ___________________________________________________________________ Name: svn:executable - * Property changes on: trunk/grassaddons/gui/icons/map.png ___________________________________________________________________ Name: svn:executable - * Property changes on: trunk/grassaddons/gui/icons/map_add.png ___________________________________________________________________ Name: svn:executable - * Property changes on: trunk/grassaddons/gui/icons/map_magnify.png ___________________________________________________________________ Name: svn:executable - * Property changes on: trunk/grassaddons/gui/icons/picture_save.png ___________________________________________________________________ Name: svn:executable - * Property changes on: trunk/grassaddons/gui/icons/printer.png ___________________________________________________________________ Name: svn:executable - * Property changes on: trunk/grassaddons/gui/icons/table_add.png ___________________________________________________________________ Name: svn:executable - * Property changes on: trunk/grassaddons/gui/icons/tag_green.png ___________________________________________________________________ Name: svn:executable - * Property changes on: trunk/grassaddons/gui/icons/zoom.png ___________________________________________________________________ Name: svn:executable - * Property changes on: trunk/grassaddons/gui/icons/zoom_in.png ___________________________________________________________________ Name: svn:executable - * Property changes on: trunk/grassaddons/gui/icons/zoom_out.png ___________________________________________________________________ Name: svn:executable - * From landa at grass.itc.it Fri Apr 20 14:30:12 2007 From: landa at grass.itc.it (landa@grass.itc.it) Date: Fri Apr 20 14:30:14 2007 Subject: [grass-addons] r533 - trunk/grassaddons/gui/gui_modules Message-ID: <200704201230.l3KCUCUB026552@grass.itc.it> Author: landa Date: 2007-04-20 14:30:12 +0200 (Fri, 20 Apr 2007) New Revision: 533 Modified: trunk/grassaddons/gui/gui_modules/debug.py Log: Debug renamed to DebugMsg Modified: trunk/grassaddons/gui/gui_modules/debug.py =================================================================== --- trunk/grassaddons/gui/gui_modules/debug.py 2007-04-20 10:09:32 UTC (rev 532) +++ trunk/grassaddons/gui/gui_modules/debug.py 2007-04-20 12:30:12 UTC (rev 533) @@ -2,12 +2,15 @@ MODULE: debug CLASSES: - * Debug + * DebugMsg PURPOSE: GRASS debugging + from debug import Debug as Debug + Debug.msg (3, 'debug message') + AUTHORS: The GRASS Development Team - Martin Landa + Martin Landa COPYRIGHT: (C) 2007 by the GRASS Development Team This program is free software under the GNU General Public @@ -17,7 +20,7 @@ import grassenv -class Debug: +class DebugMsg: """ GRASS Debugging @@ -55,7 +58,7 @@ print "GUI D%d/%d: %s" % (level, level, message) # Debug instance -Debug = Debug() +Debug = DebugMsg() # testing if __name__ == "__main__": From chemin at grass.itc.it Fri Apr 20 15:19:20 2007 From: chemin at grass.itc.it (chemin@grass.itc.it) Date: Fri Apr 20 15:19:24 2007 Subject: [grass-addons] r534 - in trunk/grassaddons/gipe: . grass_starter Message-ID: <200704201319.l3KDJKXN027212@grass.itc.it> Author: chemin Date: 2007-04-20 15:05:39 +0200 (Fri, 20 Apr 2007) New Revision: 534 Added: trunk/grassaddons/gipe/grass_starter/ trunk/grassaddons/gipe/grass_starter/GRASSnews.sty trunk/grassaddons/gipe/grass_starter/Makefile trunk/grassaddons/gipe/grass_starter/article_GRASS.tex trunk/grassaddons/gipe/grass_starter/article_QGIS.tex trunk/grassaddons/gipe/grass_starter/grass000.png trunk/grassaddons/gipe/grass_starter/grass001.png trunk/grassaddons/gipe/grass_starter/grass002.png trunk/grassaddons/gipe/grass_starter/grass003.png trunk/grassaddons/gipe/grass_starter/grass004.png trunk/grassaddons/gipe/grass_starter/grass005.png trunk/grassaddons/gipe/grass_starter/grass006.png trunk/grassaddons/gipe/grass_starter/grass007.png trunk/grassaddons/gipe/grass_starter/grass008.png trunk/grassaddons/gipe/grass_starter/grass009.png trunk/grassaddons/gipe/grass_starter/grass010.png trunk/grassaddons/gipe/grass_starter/grass011.png trunk/grassaddons/gipe/grass_starter/grass012.png trunk/grassaddons/gipe/grass_starter/grass013.png trunk/grassaddons/gipe/grass_starter/grass014.png trunk/grassaddons/gipe/grass_starter/grass015.png trunk/grassaddons/gipe/grass_starter/grass016.png trunk/grassaddons/gipe/grass_starter/grass017.png trunk/grassaddons/gipe/grass_starter/grass018.png trunk/grassaddons/gipe/grass_starter/grass019.png trunk/grassaddons/gipe/grass_starter/grass020.png trunk/grassaddons/gipe/grass_starter/grass021.png trunk/grassaddons/gipe/grass_starter/grass022.png trunk/grassaddons/gipe/grass_starter/grass023.png trunk/grassaddons/gipe/grass_starter/grass024.png trunk/grassaddons/gipe/grass_starter/grass025.png trunk/grassaddons/gipe/grass_starter/grass026.png trunk/grassaddons/gipe/grass_starter/grass027.png trunk/grassaddons/gipe/grass_starter/grass_osgeo_logo.jpg trunk/grassaddons/gipe/grass_starter/main_document.aux trunk/grassaddons/gipe/grass_starter/main_document.out trunk/grassaddons/gipe/grass_starter/main_document.tex trunk/grassaddons/gipe/grass_starter/main_document.toc trunk/grassaddons/gipe/grass_starter/qgis000.png trunk/grassaddons/gipe/grass_starter/qgis001.png trunk/grassaddons/gipe/grass_starter/qgis002.png trunk/grassaddons/gipe/grass_starter/qgis003.png trunk/grassaddons/gipe/grass_starter/qgis004.png trunk/grassaddons/gipe/grass_starter/qgis005.png trunk/grassaddons/gipe/grass_starter/qgis006.png trunk/grassaddons/gipe/grass_starter/qgis007.png trunk/grassaddons/gipe/grass_starter/qgis008.png trunk/grassaddons/gipe/grass_starter/qgis009.png trunk/grassaddons/gipe/grass_starter/qgis010.png trunk/grassaddons/gipe/grass_starter/qgis011.png trunk/grassaddons/gipe/grass_starter/qgis012.png trunk/grassaddons/gipe/grass_starter/qgis013.png trunk/grassaddons/gipe/grass_starter/qgis014.png trunk/grassaddons/gipe/grass_starter/qgis015.png trunk/grassaddons/gipe/grass_starter/qgis016.png trunk/grassaddons/gipe/grass_starter/qgis017.png trunk/grassaddons/gipe/grass_starter/qgis018.png trunk/grassaddons/gipe/grass_starter/qgis019.png trunk/grassaddons/gipe/grass_starter/qgis020.png trunk/grassaddons/gipe/grass_starter/qgis021.png trunk/grassaddons/gipe/grass_starter/qgis022.png trunk/grassaddons/gipe/grass_starter/qgis023.png trunk/grassaddons/gipe/grass_starter/qgis024.png trunk/grassaddons/gipe/grass_starter/qgis025.png trunk/grassaddons/gipe/grass_starter/qgis026.png trunk/grassaddons/gipe/grass_starter/qgis027.png trunk/grassaddons/gipe/grass_starter/qgis028.png trunk/grassaddons/gipe/grass_starter/qgis029.png trunk/grassaddons/gipe/grass_starter/qgis030.png trunk/grassaddons/gipe/grass_starter/qgis031.png trunk/grassaddons/gipe/grass_starter/qgis032.png trunk/grassaddons/gipe/grass_starter/qgis033.png trunk/grassaddons/gipe/grass_starter/qgis034.png trunk/grassaddons/gipe/grass_starter/qgis035.png trunk/grassaddons/gipe/grass_starter/qgis036.png trunk/grassaddons/gipe/grass_starter/qgis037.png trunk/grassaddons/gipe/grass_starter/qgis038.png trunk/grassaddons/gipe/grass_starter/qgis039.png trunk/grassaddons/gipe/grass_starter/qgis040.png trunk/grassaddons/gipe/grass_starter/qgis041.png trunk/grassaddons/gipe/grass_starter/qgis042.png trunk/grassaddons/gipe/grass_starter/qgis043.png trunk/grassaddons/gipe/grass_starter/qgis044.png Log: Added Beginners Tutorial for GRASS GIS, QGIS and GRASS plugin for QGIS, works for all platform, but was made for Windows initially Added: trunk/grassaddons/gipe/grass_starter/GRASSnews.sty =================================================================== --- trunk/grassaddons/gipe/grass_starter/GRASSnews.sty (rev 0) +++ trunk/grassaddons/gipe/grass_starter/GRASSnews.sty 2007-04-20 13:05:39 UTC (rev 534) @@ -0,0 +1,197 @@ +%% +%% DO NOT EDIT THIS FILE! +%% +%% This is file `GRASSnews.sty', +%% derived from 'RNews.sty' file +%% kindly provided by the R Project.. +%% +\def\fileversion{v0.2} +\def\filename{GRASSnews} +\def\filedate{2004/08/06} +\def\docdate {2001/10/31} +%% +%% Package `GRASSnews' to use with LaTeX2e +%% Copyright (C) 2004 GRASS Development Team +%% +%% -*- LaTeX -*- +\NeedsTeXFormat{LaTeX2e}[1995/12/01] +\ProvidesPackage{\filename}[\filedate\space\fileversion\space + GRASSnews package] +\typeout{Package: `\filename\space\fileversion \@spaces <\filedate>'} +\typeout{English documentation as of <\docdate>} +\RequirePackage{ifthen} +\newboolean{GRASSnews@driver} +\DeclareOption{driver}{\setboolean{GRASSnews@driver}{true}} +\DeclareOption*{\PackageWarning{\filename}{Unknown option + `\CurrentOption'}} +\ProcessOptions\relax +\ifthenelse{\boolean{GRASSnews@driver}}{}{ +\RequirePackage{multicol,graphicx,color,fancyhdr,hyperref} +\newcommand{\volume}[1]{\def\GRASSnews@volume{#1}} +\newcommand{\volnumber}[1]{\def\GRASSnews@number{#1}} +\renewcommand{\date}[1]{\def\GRASSnews@date{#1}} +\setcounter{secnumdepth}{-1} +\renewcommand{\author}[1]{\def\GRASSnews@author{#1}} +\renewcommand{\title}[1]{\def\GRASSnews@title{#1}} +\newcommand{\subtitle}[1]{\def\GRASSnews@subtitle{#1}} +\newenvironment{article}{% + \author{}\title{}\subtitle{}}{\end{multicols}} +\renewcommand{\maketitle}{ + \begin{multicols}{0}[\chapter{\GRASSnews@title}\refstepcounter{chapter}][3cm] + \ifx\empty\Rnews@subtitle\else\noindent\textbf{\GRASSnews@subtitle} + \par\nobreak\addvspace{\baselineskip}\fi + \ifx\empty\GRASSnews@author\else\noindent\textit{\GRASSnews@author} + \par\nobreak\addvspace{\baselineskip}\fi + \@afterindentfalse\@nobreaktrue\@afterheading} +\renewcommand\chapter{\secdef\GRASSnews@chapter\@schapter} +\providecommand{\nohyphens}{% + \hyphenpenalty=10000\exhyphenpenalty=10000\relax} +\newcommand{\GRASSnews@chapter}{% + \renewcommand{\@seccntformat}[1]{}% + \@startsection{chapter}{0}{0mm}{% + -2\baselineskip \@plus -\baselineskip \@minus -.2ex}{\p@}{% + \normalfont\Huge\bfseries\raggedright}} +\renewcommand*\l@chapter{\@dottedtocline{0}{0pt}{1em}} +\def\@schapter#1{\section*#1} +\renewenvironment{figure}[1][]{% + \def\@captype{figure} + \noindent + \begin{minipage}{\columnwidth}}{% + \end{minipage}\par\addvspace{\baselineskip}} +\renewcommand{\theequation}{\@arabic\c@equation} +\def\equation{% + \let\refstepcounter\H@refstepcounter + \H@equation + \def\newname{\arabic{chapter}.\theequation}% + \let\theHequation\newname% + \hyper@makecurrent{equation}% + \Hy@raisedlink{\hyper@anchorstart{\@currentHref}}% + \let\refstepcounter\new@refstepcounter}% +\def\endequation{\Hy@raisedlink{\hyper@anchorend}\H@endequation} +\renewcommand{\thefigure}{\@arabic\c@figure} +\renewcommand{\thetable}{\@arabic\c@table} +\renewcommand{\contentsname}{Contents of this volume:} +\renewcommand\tableofcontents{% + \section*{\contentsname + \@mkboth{% + \MakeUppercase\contentsname}{\MakeUppercase\contentsname}}% + \@starttoc{toc}} +\renewcommand{\titlepage}{% + \noindent + %\rule{\textwidth}{1pt}\\[-.8\baselineskip] + %\rule{\textwidth}{.5pt} + + \begin{center} + \includegraphics[height=0.75cm]{grass_osgeo_logo} + \hspace{7mm} + \fontsize{1cm}{1cm}\selectfont + FOSSGIS/ Training Manual + \end{center} +% The Newsletter of the GRASS Project\hfill +%\begin{small} +\textbf{Open Source GIS and Remote Sensing Tutorials} +%\textbf{G}eographic \textbf{R}esources \textbf{A}nalysis \textbf{S}upport \textbf{S}ystem +%\end{small} +\hfill + Version \GRASSnews@volume, \GRASSnews@date\\[-.5\baselineskip] + \rule{\textwidth}{.5pt}\\[-.7\baselineskip] + \rule{\textwidth}{2pt} + \vspace{1cm} + \fancyhf{} + \fancyhead[L]{GRASS-Tutorials} + \fancyhead[R]{Ver.~\GRASSnews@volume, \GRASSnews@date} + \fancyfoot[L]{ISSN unknown} + \fancyfoot[R]{\thepage} + \thispagestyle{empty} + \begin{bottombox} + \begin{multicols}{2} + \setcounter{tocdepth}{0} + \tableofcontents + \setcounter{tocdepth}{2} + \end{multicols} + \end{bottombox}} +\setlength{\textheight}{240mm} +\setlength{\topmargin}{0mm} +\setlength{\textwidth}{17cm} +\setlength{\oddsidemargin}{-6mm} +\setlength{\columnseprule}{.1pt} +\setlength{\columnsep}{20pt} +\RequirePackage{ae,mathpple} +\RequirePackage[T1]{fontenc} +\renewcommand{\rmdefault}{ppl} +\renewcommand{\sfdefault}{aess} +\renewcommand{\ttdefault}{aett} +\definecolor{Red}{rgb}{0.7,0,0} +\definecolor{Blue}{rgb}{0,0,0.8} +\definecolor{hellgrau}{rgb}{0.55,0.55,0.55} +\newcommand{\R}{R} +\newcommand{\address}[1]{\addvspace{\baselineskip}\noindent\emph{#1}} +\newcommand{\email}[1]{\href{mailto:#1}{\normalfont\texttt{#1}}} +\newsavebox{\GRASSnews@box} +\newlength{\GRASSnews@len} +\newenvironment{bottombox}{% + \begin{figure*}[b] + \begin{center} + \noindent + \begin{lrbox}{\GRASSnews@box} + \begin{minipage}{0.99\textwidth}}{% + \end{minipage} + \end{lrbox} + \addtolength{\GRASSnews@len}{\fboxsep} + \addtolength{\GRASSnews@len}{\fboxrule} + \hspace*{-\GRASSnews@len}\fbox{\usebox{\GRASSnews@box}} + \end{center} + \end{figure*}} +\RequirePackage{verbatim} +\def\boxedverbatim{% + \def\verbatim@processline{% + {\setbox0=\hbox{\the\verbatim@line}% + \hsize=\wd0 \the\verbatim@line\par}}% + \@minipagetrue + \@tempswatrue + \setbox0=\vbox + \bgroup\small\verbatim +} +\def\endboxedverbatim{% + \endverbatim + \unskip\setbox0=\lastbox + \egroup + \fbox{\box0} +} +\pagestyle{fancy} +} % \ifthenelse{\boolean{GRASSnews@driver}} +\newcommand\code{\bgroup\@codex} +\def\@codex#1{{\normalfont\ttfamily\hyphenchar\font=-1 #1}\egroup} +\newcommand{\kbd}[1]{{\normalfont\texttt{#1}}} +\newcommand{\key}[1]{{\normalfont\texttt{\uppercase{#1}}}} +\newcommand\samp{`\bgroup\@noligs\@sampx} +\def\@sampx#1{{\normalfont\texttt{#1}}\egroup'} +\newcommand{\var}[1]{{\normalfont\textsl{#1}}} +\let\env=\code +\newcommand{\file}[1]{{`\normalfont\textsf{#1}'}} +\let\command=\code +\let\option=\samp +\newcommand{\dfn}[1]{{\normalfont\textsl{#1}}} +\newcommand{\acronym}[1]{{\normalfont\textsc{\lowercase{#1}}}} +\newcommand{\strong}[1]{{\normalfont\fontseries{b}\selectfont #1}} +\let\pkg=\strong +\RequirePackage{alltt} +\newenvironment{example}{\begin{alltt}}{\end{alltt}} +\newenvironment{smallexample}{\begin{alltt}\small}{\end{alltt}} +\newenvironment{display}{\list{}{}\item\relax}{\endlist} +\newenvironment{smallverbatim}{\small\verbatim}{\endverbatim} +\providecommand{\operatorname}[1]{% + \mathop{\operator@font#1}\nolimits} +\renewcommand{\P}{% + \mathop{\operator@font I\hspace{-1.5pt}P\hspace{.13pt}}} +\newcommand{\E}{% + \mathop{\operator@font I\hspace{-1.5pt}E\hspace{.13pt}}} +\newcommand{\VAR}{\operatorname{var}} +\newcommand{\COV}{\operatorname{cov}} +\newcommand{\COR}{\operatorname{cor}} +\RequirePackage{amsfonts} +\endinput + + +%% +%% End of file `GRASSnews.sty'. Added: trunk/grassaddons/gipe/grass_starter/Makefile =================================================================== --- trunk/grassaddons/gipe/grass_starter/Makefile (rev 0) +++ trunk/grassaddons/gipe/grass_starter/Makefile 2007-04-20 13:05:39 UTC (rev 534) @@ -0,0 +1,64 @@ +#convenient Makefile (I think) -MN + +#targets: +# make - builds the text +# make refcheck - check \label{} and \ref{} and friends for consistency +# Result is in "latex_refs.log" +# make clean - removed Latex' auxiliary files +# make ps - create PS file +# make pdf - create PDF file +# make html - create HTML files (into GRASSnews_volX/ subdirectory) +# make ascii - extracts ASCII from original ${LATEX} files (into ascii/ subdirectory) +# make changelog - creates ChangeLog + +FILE=main_document +LATEX=pdflatex + +all: + ${LATEX} $(FILE) + #bibtex $(FILE) + #now loop over ${LATEX} files, until stable: + echo Rerun > $(FILE).log + while grep Rerun $(FILE).log >/dev/null 2>&1 ; do ${LATEX} $(FILE).tex ; done + +#check references: +refcheck: all + ${LATEX} $(FILE) | grep "undefined" > latex_refs.log + #@echo "Bibtex errors:" >> latex_refs.log + #bibtex $(FILE) | grep 'find a database' >> latex_refs.log + @echo "Check is stored in: latex_refs.log" + @cat latex_refs.log + +#make PostScript: +ps: all + dvips -o $(FILE).ps $(FILE).dvi + +#make PDF: +pdf: all + @#the new converter script has a different name: + @(type -p dvipdfpress > /dev/null ; if [ $$? -eq 0 ] ; then dvipdfpress $(FILE).dvi $(FILE).pdf ; else dvipdf $(FILE).dvi $(FILE).pdf ; fi ) + @echo "Generated: $(FILE).pdf" + +html: + $(MAKE) all + latex2html -init_file l2h.conf -split=+2 -address "(C) 2005, GRASS Newsletter editorial board
Last modified: `/bin/date +%d-%m-%Y`" $(FILE) + @echo "HTML Newsletter generated in ./$(FILE)" + +# set to your preferred software such as detex, untex, ... +DETEX=detex +ascii: + @test -d ascii || mkdir ascii ; true + @(for i in *.tex ; do n=`basename $$i .tex` ; ${DETEX} $$i > ascii/$$n.txt ; done) + @echo "ASCII newsletter extracted to ./ascii/" + +clean: + rm -f *.log $(FILE).aux $(FILE).dvi $(FILE).bbl $(FILE).blg $(FILE).out $(FILE).cb $(FILE).toc + +veryclean: clean + rm -f *~ latex_refs.log *.ps *.pdf + +# cvs2cl.pl creates a GNU style ChangeLog file: +# http://www.red-bean.com/~kfogel/cvs2cl.shtml +changelog: + cvs2cl.pl + @echo "Written ChangeLog" Added: trunk/grassaddons/gipe/grass_starter/article_GRASS.tex =================================================================== --- trunk/grassaddons/gipe/grass_starter/article_GRASS.tex (rev 0) +++ trunk/grassaddons/gipe/grass_starter/article_GRASS.tex 2007-04-20 13:05:39 UTC (rev 534) @@ -0,0 +1,816 @@ +% Template GRASS newsletter - Article +% Language: Latex +% + +% Head + +\title{Starter Manual to GRASS GIS} +\subtitle{GRASS Original} +\author{GRASS Development Team} + +\maketitle + +\section{Introduction} + +This screenshot is GRASS GIS running natively on Windows +(http://geni.ath.cx/grass.html).Fig.~\ref{fig:grass000} +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.15]{grass000.png} + %caption of the figure + \caption{Windows Native GRASS} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass000} +\end{figure} + +Starting GRASS GIS: Select Spearfish60 Location by a click on +``Enter GRASS'':Fig.~\ref{fig:grass001} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.3]{grass001.png} + %caption of the figure + \caption{Welcome Screen} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass001} +\end{figure} + +At this point it should (somewhat) look this way: Fig.~\ref{fig:grass002} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{grass002.png} + %caption of the figure + \caption{GIS Manager} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass002} +\end{figure} + +Some more GRASS GUI information is found in Appendix A \ref{appendixA}. + +Load the elevation.10m layer by clicking on the raster display button +(second button from left):Fig.~\ref{fig:grass003} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.5]{grass003.png} + %caption of the figure + \caption{Basic Display} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass003} +\end{figure} + +Once selected, the main GRASS GUI will have a new layer like one of +these:Fig.~\ref{fig:grass004} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{grass004.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass004} +\end{figure} + +By selecting the new layer, you will be given a contextual menu below:Fig.~\ref{fig:grass005} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{grass005.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass005} +\end{figure} + +Then add a vector using the 8th icon from the left. Fig.~\ref{fig:grass006} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.5]{grass006.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass006} +\end{figure} + +Add a stream layer (blue color) and a road layer (brown color). Below is +the example for the streams Fig.~\ref{fig:grass007} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.45]{grass007.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass007} +\end{figure} + +Result should look like this (somehow):Fig.~\ref{fig:grass008} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{grass008.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass008} +\end{figure} + +\section{DEM MANIPULATIONS} +Display a dem:Fig.~\ref{fig:grass009} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{grass009.png} + %caption of the figure + \caption{Display a DEM} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass009} +\end{figure} + +\subsection{Compute slope and aspect} +Raster/Terrain Analysis/Slope and Aspect. Fig.~\ref{fig:grass010} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{grass010.png} + %caption of the figure + \caption{Slope} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass010} +\end{figure} + +Compute a shaded relief map ( in Raster/Terrain Analysis/Shaded Relief Map). +The Shaded Relief map should be like this when a elevation.10m map is overlaid to it with 0.75 opacity: Fig.~\ref{fig:grass011} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{grass011.png} + %caption of the figure + \caption{Shaded Relief} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass011} +\end{figure} + +\subsection{Watershed Basin Analysis Program} +Raster/Hydrologic modeling/Watershed Analysis. +Fill in the Elevation input map with ``elevation.10m''. The minimum size of an exterior watershed basin should be 5000 cells. Fill up output names for all the output maps available (i.e. ``\_cells\_nbr'', ``\_drain\_dir'', ``\_drain\_dir'', ``\_basins'', ``\_streams'', ``\_half\_basins'', ``\_visual'', ``\_LS'', ``\_S''). + +Output should state the following: + +SECTION 1a (of 6): Initiating Memory. + +SECTION 1b (of 6): Determining Offmap Flow. + +SECTION 2: A * Search. + +SECTION 3: Accumulating Surface Flow. + +SECTION 4: Length Slope determination. + +SECTION 5: Watershed determination. + +SECTION 6: Closing Maps. + +Output of ``\_basins'' should look like this:Fig.~\ref{fig:grass012} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{grass012.png} + %caption of the figure + \caption{Generated Basins} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass012} +\end{figure} + + +Output of ``\_streams'' should look like this: Fig.~\ref{fig:grass013} compare it with +the vector map ``streams''. + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{grass013.png} + %caption of the figure + \caption{Generated streams} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass013} +\end{figure} + +Relaunch with various values instead of 5000 cells, i.e. 2000 and 10000. +Compare by vectorizing the streams generated. Vectorization follows +these steps: +1-Raster/Neighborhood Analysis/Thin Linear Features +2-File/Map Type Conversion/Raster to Vector Map +3-Vector/Develop Map/Create-Rebuild Topology (optional) + +\subsection{Stream Pollution Monitoring Station site identification} +Assuming a Wood Processing Factory is requesting permit to setup a new processing plant in the country side. It is remote from major mapped streams (598713.35(E) 4920069.15(N)), the local council gave you a notice to assess the path of some minor effluents that may be draining to the major stream from the future factory, and especially their meeting point coordinates where the council will establish an automatic monitoring station. +Use Raster/Terrain Analysis/Least Cost Route Or Flow, your output should look like this: Fig.~\ref{fig:grass014} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{grass014.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass014} +\end{figure} + +What is the location (Easting,Northing) of the generated stream crossing +the mapped stream where it is proposed to install a monitoring +station? + +\section{GRASS GIS Habitat Analysis} +\subsection{Introduction} + +http://www.udel.edu/johnmack/frec682/682proj2.html + +This course is available online under the course name ``FREC 682 Spatial Analysis''. This material is a modified version to accommodate with GRASS 6.3+. + +In this session, the features needed are the following essentially (look in RASTER from the main interface): +The BUFFERING Module: Raster/Create buffers Fig.~\ref{fig:grass015} +The MAP CALCULATOR Module: Raster/Map Calculator Fig.~\ref{fig:grass016} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{grass015.png} + %caption of the figure + \caption{BUFFERING} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass015} +\end{figure} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{grass016.png} + %caption of the figure + \caption{MAP CALCULATOR} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass016} +\end{figure} + +Additional modules for the later part of the lab + +Query map with mouse: +In the Map display window, look for: Fig.~\ref{fig:grass017} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=1]{grass017.png} + %caption of the figure + \caption{Query Map with mouse} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass017} +\end{figure} + +NULL values: Raster/Develop Map/Manage Null values + +CLUMP: Raster/Transform features/Clump small Areas + +STATISTICS: Raster/Reports \& Statistics = General statistics + +R2V: File/Map type Conversions/Raster to vector + +Vector Build: Vector/Develop Map/Create/Rebuild Topology + +Vector Export: File/Export/Vector map/Various format using OGR (SHAPE, etc) + +Start New Display Window: +In the main GRASS GUI look for: Fig.~\ref{fig:grass018} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=1]{grass018.png} + %caption of the figure + \caption{Launch New Display} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass018} +\end{figure} + +Erase Display: +In the Map display window, look for: Fig.~\ref{fig:grass019} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=1]{grass019.png} + %caption of the figure + \caption{Erase Display Icon} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass019} +\end{figure} + +Redraw map: +In the Map display window, look for: Fig.~\ref{fig:grass020} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=1]{grass020.png} + %caption of the figure + \caption{Redraw Icon} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass020} +\end{figure} + +\subsection{HABITAT PRESERVATION MISSION} +The pickled strumpet (Trollopensus bibulosa) has recently been added to the Endangered Species List, and the Fish and Wildlife Service is identifying likely habitat areas in the Spearfish area for protection from development. They have constructed the following habitat scoring system based on the species observed habitat preferences: + +\subsection{HABITAT SCORING SYSTEM (from Fish and Wildlife Service) } + +Map number Environmental conditions Score to be given +1 within 500 meters of streams where slope <= 5 degrees +2 points +2 within 500 meters of streams where slope >5 degrees +5 points +3 within 500 meters of a road -5 points +4 coniferous forest +4 points +5 mixed forest +1 point +6 northern exposure (aspect from NW to NE) +3 points +7 western or eastern exposure (SW to NW or SE to NE) +1 point +8 1200-1400 meters elevation +2 points +9 1400-1600 meters elevation +4 points +10 over 1600 meters elevation +2 points + +Use r.buffer (..=> Create buffers) and r.mapcalc (..=> Map calculator) to create an aggregate habitat score map of the entire area, summing all the partial scores as defined above. (hint: you have to convert all null values of buffer output maps into zero values) +When finished with numbers 1 to 10 above, sum all the maps in a map that you may call scoreindivsum. Then identify suitable habitat areas by converting to zeroes all cells with overall habitat scores below 9, and all cells within 100 meters of a road (hint: you have to make a new buffer map here, and remove its null values for calculations). Make a final scoring map that you may call scorefinal and change 0 values to NULL (..=>Manage null values) for next laboratory part. + +\subsection{PROCESSING THE SCORING MAPS} + +In the processing of number 1 and 2, please use the Map Calculator with an input following an 'if statement'. The structure goes this +way: +\begin{smallverbatim} +if(condition, action_if_true, action_if_not_true) +\end{smallverbatim} +In the case of a map application use it this way: +\begin{smallverbatim} +if(MapA==value1, score_value1, score_value2) +\end{smallverbatim} +Since one may want to give two input maps together, a double if statement can be formulated this way: + +A practical example for number 1: +\begin{smallverbatim} +if(stream_buff_500==2,if(slope=5,2,0),0) +\end{smallverbatim} +In case one wants to select a range of values (inclusive or exclusive), use of OR and AND is necessary. Query to check aspect map (..=>Query with mouse), East=1 and North=+90 degrees. + +Practical examples for 7a and 7b: +\begin{smallverbatim} +7a) if(aspect<225 && aspect>135, 1, 0) + +7b) if(aspect<45 || aspect>314 && aspect!=0, 1, 0) +\end{smallverbatim} +Example 7b has an added constraint ``\&\& aspect !=0'' because aspect value 0 means no aspect was calculated (usually out of data boundary in +the image). + +This Set of instructions shows how GRASS GIS does the reclassification work under scripting mode. This is useful when you need to reuse the same set of GIS manipulations/models several times on different or new datasets. + +\subsection{HABITAT SCORING SCRIPT} + +Map number Environmental conditions Score to be given: + +\textbf{ +1) within 500 meters of streams where slope <= 5 degrees: +2 points} +\begin{smallverbatim} +r.buffer input=streams output=_bstreams500 + distances=500 units=meters --overwrite + +r.null map=_bstreams500 null=0 + +r.mapcalc _rbstreams500="if(_bstreams500==2,1,0)" + +r.null map=_rbstreams500 null=0 +\end{smallverbatim} + +\textbf{ +2) within 500 meters of streams where slope >5 degrees: +5 points} +\begin{smallverbatim} +r.buffer input=roads output=_broads500 distances=500 + units=meters --overwrite + +r.mapcalc _s_sl="if(_rbstreams500==1,if( + slope<=5,2,5),0)" +\end{smallverbatim} + +\textbf{ +3) within 500 meters of a road: -5 points} +\begin{smallverbatim} +r.mapcalc _rbroads500="float(if( + _broads500==2,-5.0,0))" +\end{smallverbatim} + +\textbf{ +4) coniferous forest: +4 points} +\begin{smallverbatim} +r.mapcalc _for="if(vegcover==3,4,0)" +\end{smallverbatim} + +\textbf{ +5) mixed forest: +1 point} +\begin{smallverbatim} +r.mapcalc _for1="if(vegcover==5,1,0)" +\end{smallverbatim} + +\textbf{ +6) northern exposure (aspect from NW to NE): +3 points} +\begin{smallverbatim} +r.mapcalc _exp3="if(aspect<=135.0 && aspect>=45.0 + && aspect != 0,3,0)" +\end{smallverbatim} + +\textbf{ +7a) western or eastern exposure (SW to NW or SE to NE): +1 point} +\begin{smallverbatim} +r.mapcalc _exp1="if(aspect<45.0 || aspect>314.0,1,0)" +r.mapcalc _exp2="if(aspect<225.0 && aspect>135.0,1,0)" +\end{smallverbatim} + +\textbf{ +7b) 1200-1400 meters elevation: +2 points} +\begin{smallverbatim} +r.mapcalc _elev1="if(elevation.10m<1400 + && elevation.10m>1200,2,0)" +\end{smallverbatim} + +\textbf{ +8) 1400-1600 meters elevation: +4 points} +\begin{smallverbatim} +r.mapcalc _elev2="if(elevation.10m<1600 + && elevation.10m>=1400,4,0)" +\end{smallverbatim} + +\textbf{ +9) over 1600 meters elevation: +2 points} +\begin{smallverbatim} +r.mapcalc _elev3="if(elevation.10m>=1600,2,0)" +\end{smallverbatim} + +\subsection{FINALIZING THE SCORING MAP} + +\begin{smallverbatim} +r.buffer input=roads output=_br100 + distances=100 units=meters --overwrite + +r.null map=_br100 null=0 + +r.mapcalc _add="float(_s_sl+_rbroads500+_for+ + _for1+_exp1+_exp2+_exp3+_elev1+_elev2+_elev3)" + +r.mapcalc _clas="if(_add>9,1,null())" + +r.mapcalc _class="if(_clas==1&&_br100==0,1,null())" +\end{smallverbatim} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{grass021.png} + %caption of the figure + \caption{Scoring Map} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass021} +\end{figure} + +\subsection{CLUMPING SUITABLE AREAS} +WARNING: This part of the Laboratory involves the use of the command line (command prompt). + +What is ``clumping''? +Recategorizes data in a raster map layer by grouping cells that form physically discrete areas into unique categories. + +Now find the discrete areas (clumps) of aggregated habitat scores. +Run \textit{r.clump (..=>Clump Small Areas) }on the ``\textit{score\_final}'' map to give each clump its own category number. You may call the output ``\textit{score\_clumped}''. + +\begin{smallverbatim} +r.clump input=score_final output=score_clumped +\end{smallverbatim} + +Display your newly clumped map ``\textit{score\_clumped}''. It should somewhat look like this one:Fig.~\ref{fig:grass022} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{grass022.png} + %caption of the figure + \caption{Clumped map} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass022} +\end{figure} + + +Since this species is most viable in larger clumps, extract the clumps greater than 50 hectares to a separate map. You may use the command line for reclassification by area threshold to select area superior to 50 hectares: + +\begin{smallverbatim} +r.reclass.area input = score_final greater=50 + output=selected_habitat_area +\end{smallverbatim} + +Output ``selected\_habitat\_area'' at this level should be similar to this one:Fig.~\ref{fig:grass023} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{grass023.png} + %caption of the figure + \caption{Seleceted Habitat Areas} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass023} +\end{figure} + +\subsection{EXPORT RESULTS TO VECTOR} +Since the users work in vector GIS, we are going to convert the results in vector format and export them from GRASS GIS to shapefile. + +Vectorize the clumped map you just produced (File/Map type conversion/raster to vector) and check that you +have effectively created a polygon vector map by displaying your vector using random colors.Fig.~\ref{fig:grass024} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{grass024.png} + %caption of the figure + \caption{Vector Export} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass024} +\end{figure} + +Export this selected habitat vector file along with the ``\textit{roads}'' and ``\textit{streams}'' vector files into shapefile. Please be sure that you export the same type of vectors (areas for ``\textit{selected\_habitat\_area}'' and lines for ``\textit{roads}'' and ``\textit{streams}''). Display and query them using Quantum GIS. + +\begin{smallverbatim} +v.out.ogr input=selected_habitat_area type=area + dsn=QGISDATA layer=1 format=ESRI_Shapefile + +v.out.ogr input=roads type=line dsn=QGISDATA + layer=1 format=ESRI_Shapefile + +v.out.ogr input=streams type=line dsn=QGISDATA + layer=1 format=ESRI_Shapefile +\end{smallverbatim} + +Additional processing..... + +The pickled strumpet is quite intolerant of edge disturbances, so you would like to weight interior areas of the habitat clumps more highly than edge areas. Create interior 100-meter-interval buffers within the large habitat clumps, where interior pixels within 100 meters of a clump edge have a weight of 1, interior pixels 100-200 meters from a clump edge have a weight of 2, interior pixels 200-300 meters from a clump edge have a weight of 3, etc., and pixels outside the largest clumps have a weight of zero. + +Use \textit{r.mapcalc} to multiply the aggregate habitat score map by these interior buffer weights. +Now run \textit{r.volume }on this buffer-weighted habitat score map to obtain the sum and average of each clump s cell habitat scores. +Use \textit{awk} to create reclass rules files, then use \textit{r.reclass} to map the largest clumps by sum and average habitat scores. +Which clump has the highest weighted total habitat score? Which has the highest weighted average habitat score? +Use \textit{r.grow} to create a map of clump edges only (subtracting the input map from the output map). +Use the edge map to index the perimeter of each of the major clumps. +Calculate the compactness (area divided by perimeter squared) of each clump. +Map the largest clumps by compactness. Create a jazzy display script to demonstrate your procedures and explain your findings. +(See \href{http://www.udel.edu/johnmack/frec682/script_ideas.html}) + +A script for some parts of the Lab is in \textbf{Appendix B} \ref{appendixB}. + +\newpage + +\subsection{Appendix A: Overview of Grass GUI} +\label{appendixA} + +Overview of the available commands in the modules:Fig.~\ref{fig:grass025} Fig.~\ref{fig:grass026} Fig.~\ref{fig:grass027} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.4]{grass025.png} + %caption of the figure + \caption{GRASS Menus (1/3)} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass025} +\end{figure} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.4]{grass026.png} + %caption of the figure + \caption{GRASS Menus (2/3)} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass026} +\end{figure} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.4]{grass027.png} + %caption of the figure + \caption{GRASS Menus (3/3)} + %label of the figure, which has to correspond to \ref{}: + \label{fig:grass027} +\end{figure} + +\newpage + +\subsection{Appendix B: GRASS SCRIPT} +\label{appendixB} + +Please put this into a file that you may name \textit{script.sh}\textup{and from a }\textit{Terminal}\textup{ type: ``chmod 0755}\textit{script.sh''}\textup{. Then you can run the script }by typing the command: ``./script.sh''. + +\begin{smallverbatim} +#!/bin/bash +# main map names variables +dem=elevation.dem +r=roads +s=streams +# buffer streams and roads variables +_bs=_bstreams500 +_br=_broads500 +# reclassed buffer streams and roads variables +_rbs=_rbstreams500 +_rbr=_rbroads500 +#-------------------------------------------------- +# General Presentation: Start Monitor 0 +#-------------------------------------------------- +d.mon start=x0 +d.erase color=grey +d.rast map=\$dem +sleep 1 +d.vect map=\$r color=brown +sleep 1 +d.vect map=\$s color=blue +sleep 1 +d.barscale bcolor=white tcolor=black at=30.0,95.0 +sleep 2 +#-------------------------------------------------- +# Buffering: Start Monitor 1 +#-------------------------------------------------- +d.mon start=x1 +d.mon select=x1 +d.erase color=grey +r.buffer input=\$s output=\$_bs distances=500 + units=meters --overwrite +r.null map=\$_bs null=0 +d.rast map=\$_bs +d.vect map=\$s color=blue +d.barscale bcolor=white tcolor=black at=30.0,95.0 +r.buffer input=\$r output=\$_br distances=500 + units=meters --overwrite +r.null map=\$_br null=0 +d.rast map=\$_br +d.vect map=\$s color=blue +d.barscale bcolor=white tcolor=black at=30.0,95.0 +#-------------------------------------------------- +# Reclassification: Start Monitor 2 +#-------------------------------------------------- +d.mon start=x2 +d.mon select=x2 +d.erase color=grey +echo "...Reclassify..." +r.mapcalc \$_rbs="if(\$_bs==2,1,0)" +r.mapcalc _s_sl="if(\$_rbs==1,if(slope<=5,2,5),0)" +r.mapcalc \$_rbr="float(if(\$_br==2,-5.0,0))" +r.mapcalc _for="if(vegcover==3,4,0)" +r.mapcalc _for1="if(vegcover==5,1,0)" +r.mapcalc _exp1="if(aspect<45.0 || aspect>314.0 && + aspect != 0.0,1,0)" +r.mapcalc _exp2="if(aspect<225.0 && aspect>135. + 0,1,0)" +r.mapcalc _exp3="if(aspect<=135.0 && aspect>=45.0, + 3,0)" +r.mapcalc _elev1="if(\$dem<1400 &&\$dem>1200,2,0)" +r.mapcalc _elev2="if(\$dem<1600 &&\$dem>=1400,4,0)" +r.mapcalc _elev3="if(\$dem>=1600,2,0)" +#-------------------------------------------------- +r.buffer input=\$r output=_br100 distances=100 + units=meters --overwrite +r.null map=\_br100 null=0 +r.mapcalc _add="float(_s_sl+\$_rbr+_for+_for1+ + _exp1+_exp2+_exp3+_elev1+_elev2+_elev3)" +r.mapcalc _clas="if(_add{>9,1,null())" +r.mapcalc _class="if(_clas==1&&_br100==0,1,null())" +echo "Reclassification...done." +d.rast map="\$_rbs" +sleep 1 +d.rast map="_s_sl" +d.rast map="\$_rbr" +d.rast map="_for" +d.rast map="_for1" +d.rast map="_exp1" +d.rast map="_exp2" +d.rast map="_exp3" +d.rast map="_elev1" +d.rast map="_elev2" +d.rast map="_elev3" +d.rast map="_br100" +sleep 1 +#r.colors color=grey map="_add" +d.rast map="_add" +sleep 1 +d.rast map="_class" +sleep 1 +#d.erase +d.vect map="\$s" color=blue +d.vect map="\$r" color=brown +d.barscale bcolor=white tcolor=black at=30.0,95.0 +sleep 5 +g.remove +rast="\$_rbs,_s_sl,\$_rbr,_for,_for1"} +g.remove +rast="_exp1,_exp2,_exp3,_elev1"} +g.remove +rast="_elev2,_elev3,_br100,_add"} +g.remove rast="\$_bs,\$_br,_clas"} +sleep 1 +echo "" +echo "finished" +sleep 1 +#-------------------------------------------------- +# Clumping: Start Monitor 3 +#-------------------------------------------------- +d.mon start=x3 +d.mon select=x3 +d.erase color=grey +g.remove rast=_clump.clump._rclumpnew +r.clump input=_class output=_clump --overwrite +r.colors color=gyr map="_clump" +d.rast map="_clump" +sleep 1 +r.reclass.area input=_clump greater=50 + output=_rclumpnew --overwrite +r.colors color=gyr map="_rclumpnew" +d.erase color=white +d.rast map="_rclumpnew" +sleep 1 +d.vect map="streams" color=blue +d.vect map="roads" color=brown +d.barscale bcolor=white tcolor=black at=30.0,95.0 +sleep 1 +g.remove rast="_clump,_class" +#-------------------------------------------------- +# R2V and Export: Start Monitor 4 +#-------------------------------------------------- +d.mon start=x4 +d.mon select=x4 +d.erase color=white +r.to.vect -s input=_rclumpnew output=rclump + feature=area --overwrite +d.vect -c map=rclump type=area color=black +d.vect map="streams" color=blue +d.vect map="roads" color=brown +d.barscale bcolor=white tcolor=black at=30.0,95.0 +sleep 1 +v.out.ogr input=rclump type=area dsn=QGISDATA + layer=1 format=ESRI_Shapefile --overwrite +g.remove rast="_rclumpnew" +g.remove vect="rclump" +d.mon stop=x4 +d.mon stop=x3 +d.mon stop=x2 +d.mon stop=x1 +d.mon stop=x0 +\end{smallverbatim} + +\address{GRASS Development Team\\ + \url{http://grass.itc.it}\\ + \email{webmaster@grass.itc.it}} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: main_document.tex +%%% End: + Added: trunk/grassaddons/gipe/grass_starter/article_QGIS.tex =================================================================== --- trunk/grassaddons/gipe/grass_starter/article_QGIS.tex (rev 0) +++ trunk/grassaddons/gipe/grass_starter/article_QGIS.tex 2007-04-20 13:05:39 UTC (rev 534) @@ -0,0 +1,639 @@ +% Template GRASS newsletter - Article +% Language: Latex +% + +% Head + +\title{Starter Manual to Quantum GIS for GRASS GIS} +\subtitle{} +\author{GRASS Development Team} + +\maketitle + +\section{QUANTUM GIS INTRODUCTION} + +This Manual is valid for QGIS version 0.8+ (http://www.qgis.org) +\textbf{Start QGIS}, the first time it should look like Fig.~\ref{fig:qgis000} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{qgis000.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis000} +\end{figure} + +Open some vector layers from QGIS sample data set Fig.~\ref{fig:qgis001} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{qgis001.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis001} +\end{figure} + +Select all the layers (Ctrl+a) Fig.~\ref{fig:qgis002} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{qgis002.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis002} +\end{figure} + +The layers displayed should look like Fig.~\ref{fig:qgis003} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{qgis003.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis003} +\end{figure} + +Zoom to all layers extents... Fig.~\ref{fig:qgis004} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.2]{qgis004.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis004} +\end{figure} + +Result after zooming to all layers Fig.~\ref{fig:qgis005} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.2]{qgis005.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis005} +\end{figure} + +Set the first layer in the overview frame Fig.~\ref{fig:qgis006} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.2]{qgis006.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis006} +\end{figure} + +Result... Fig.~\ref{fig:qgis007} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.2]{qgis007.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis007} +\end{figure} + +Open the plugin menu Fig.~\ref{fig:qgis008} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{qgis008.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis008} +\end{figure} + +It should look like this Fig.~\ref{fig:qgis009} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{qgis009.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis009} +\end{figure} + +Select those plugins Fig.~\ref{fig:qgis010} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{qgis010.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis010} +\end{figure} + + +Some new menus have appeared! Fig.~\ref{fig:qgis011} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{qgis011.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis011} +\end{figure} + + +Maximizing QGIS makes more icons appearing... Fig.~\ref{fig:qgis012} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.2]{qgis012.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis012} +\end{figure} + + +Drag the new menus below to make them glue to a second level of +toolbars... Fig.~\ref{fig:qgis013} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.2]{qgis013.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis013} +\end{figure} + +\section{QUANTUM GIS GRASS PLUGIN} + +Open a GRASS raster layer by clicking on the second button from the +left Fig.~\ref{fig:qgis014} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.45]{qgis014.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis014} +\end{figure} + +This is the contextual menu that opens, select the Map name as +``elevation.10m'' Fig.~\ref{fig:qgis015} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.85]{qgis015.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis015} +\end{figure} + + +This is the result of loading the GRASS Raster Layer Fig.~\ref{fig:qgis016} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.2]{qgis016.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis016} +\end{figure} + +Very similarly with other types of data, add the layer to overview Fig.~\ref{fig:qgis017} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.2]{qgis017.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis0017} +\end{figure} + +Result Fig.~\ref{fig:qgis018} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.2]{qgis018.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis018} +\end{figure} + +Add a GRASS Vector Layer by selecting the first Icon from the left Fig.~\ref{fig:qgis019} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.75]{qgis019.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis019} +\end{figure} + +This is the contextual menu that opens, select the Map name as +``streams'' and the layer name as ``1\_Line'' Fig.~\ref{fig:qgis020} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.45]{qgis020.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis020} +\end{figure} + +This is the streams vector layer, open the properties by a right{}-click +on the name Fig.~\ref{fig:qgis021} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.2]{qgis021.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis021} +\end{figure} + +The Properties box looks like this, and select the ``fill color'' button +to open a color selection dialog box. Change the color to a common Blue +and apply Fig.~\ref{fig:qgis022} Fig.~\ref{fig:qgis023} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{qgis022.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis022} +\end{figure} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{qgis023.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis023} +\end{figure} + +Select the first button on the right side to start GRASS vector editing +module Fig.~\ref{fig:qgis024} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.4]{qgis024.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis024} +\end{figure} + +The Edit GRASS Vector dialog box can only be opened when a vector is +selected in the main QGIS window Fig.~\ref{fig:qgis025} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{qgis025.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis025} +\end{figure} + +Select the ``moving vertex'' button (5th from the +left) and move the red cross on the map Fig.~\ref{fig:qgis026} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{qgis026.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis026} +\end{figure} + +The result should look like this (Fig.~\ref{fig:qgis026}). The last button in the toolbar will +commit the modifications to the vector layer and rebuild it Fig.~\ref{fig:qgis027} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.75]{qgis027.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis027} +\end{figure} + +In the launching terminal, the commit changes are described Fig.~\ref{fig:qgis028} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{qgis028.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis028} +\end{figure} + +Set GRASS plugin environment for processing... Fig.~\ref{fig:qgis029} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{qgis029.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis029} +\end{figure} + +Following this, select the 3rd icon from the left on +the GRASS toolbar. This will open the GRASS processing tool as shown +(mostly) in the next three pages. This GRASS tool is a thin +representation of the GRASS GIS capacities, but it will serve the +purpose of this introduction. It comes with a browser of the GRASS +mapset used. It also acts as a data management interface. Fig.~\ref{fig:qgis030} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.4]{qgis030.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis030} +\end{figure} + +The browser has the capacity to expand header information and metadata +pertaining to the layer selected Fig.~\ref{fig:qgis031} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.3]{qgis031.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis031} +\end{figure} + +The GRASS modules available are listed in the next two pages. More are +being ported everyday, The actual number of GRASS GIS modules exceeds +400, you can see that there is still some work to do, and the community +of volunteers are working on it Fig.~\ref{fig:qgis032} Fig.~\ref{fig:qgis033} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.4]{qgis032.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis032} +\end{figure} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.4]{qgis033.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis033} +\end{figure} + +\subsection{GRASS PLUGIN PROCESSING} + +Let us create some buffers... Select buffering of vectors from the +Modules list. It should looks like this. Choose 500 meters buffer +size Fig.~\ref{fig:qgis034} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.3]{qgis034.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis034} +\end{figure} + +Processing is going on... Fig.~\ref{fig:qgis035} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.3]{qgis035.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis035} +\end{figure} + +Finishing the processing Fig.~\ref{fig:qgis036} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.3]{qgis036.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis036} +\end{figure} + +Result should look like this (you have to load the map yourself!) Fig.~\ref{fig:qgis037} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.2]{qgis037.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis037} +\end{figure} + +Now create another buffer from streams but this time at 100 meters... +Like this Fig.~\ref{fig:qgis038} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.2]{qgis038.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis038} +\end{figure} + +Now we are going to subtract the 100m buffer to the 500m buffer, because +we want to exclude the streams and its proximity from our area of +selection. Find this module! Fig.~\ref{fig:qgis039} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{qgis039.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis039} +\end{figure} + +Processing the overlay with boolean operator ``NOT'' Fig.~\ref{fig:qgis040} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.35]{qgis040.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis040} +\end{figure} + +Result is discarding all under 100m from the streams, and all above 500 +meters from the streams. Fig.~\ref{fig:qgis041} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.2]{qgis041.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis041} +\end{figure} + +Process an aspect map from the elevation map Fig.~\ref{fig:qgis042} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.45]{qgis042.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis042} +\end{figure} + +Processing... Fig.~\ref{fig:qgis043} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.45]{qgis043.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis043} +\end{figure} + +Result Fig.~\ref{fig:qgis044} + +%\setkeys{Gin}{width=1\textwidth} +\begin{figure}[htbp] + \centering + %name of your graphic, without the path AND in PNG (screnshots etc)/PDF (drawings) format: + \includegraphics[scale=0.2]{qgis044.png} + %caption of the figure + \caption{} + %label of the figure, which has to correspond to \ref{}: + \label{fig:qgis044} +\end{figure} + +\address{GRASS Development Team\\ + \url{http://grass.itc.it}\\ + \email{webmaster@grass.itc.it}} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: main_document.tex +%%% End: + Added: trunk/grassaddons/gipe/grass_starter/grass000.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass000.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass001.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass001.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass002.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass002.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass003.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass003.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass004.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass004.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass005.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass005.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass006.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass006.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass007.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass007.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass008.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass008.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass009.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass009.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass010.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass010.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass011.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass011.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass012.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass012.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass013.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass013.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass014.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass014.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass015.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass015.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass016.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass016.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass017.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass017.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass018.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass018.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass019.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass019.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass020.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass020.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass021.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass021.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass022.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass022.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass023.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass023.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass024.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass024.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass025.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass025.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass026.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass026.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass027.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass027.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/grass_osgeo_logo.jpg =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/grass_osgeo_logo.jpg ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/main_document.aux =================================================================== --- trunk/grassaddons/gipe/grass_starter/main_document.aux (rev 0) +++ trunk/grassaddons/gipe/grass_starter/main_document.aux 2007-04-20 13:05:39 UTC (rev 534) @@ -0,0 +1,182 @@ +\relax +\ifx\hyper@anchor\@undefined +\global \let \oldcontentsline\contentsline +\gdef \contentsline#1#2#3#4{\oldcontentsline{#1}{#2}{#3}} +\global \let \oldnewlabel\newlabel +\gdef \newlabel#1#2{\newlabelxx{#1}#2} +\gdef \newlabelxx#1#2#3#4#5#6{\oldnewlabel{#1}{{#2}{#3}}} +\AtEndDocument{\let \contentsline\oldcontentsline +\let \newlabel\oldnewlabel} +\else +\global \let \hyper@last\relax +\fi + +\@writefile{toc}{\contentsline {chapter}{Starter Manual to Quantum GIS for GRASS GIS}{1}{section*.2}} +\@writefile{toc}{\contentsline {section}{QUANTUM GIS INTRODUCTION}{1}{section*.3}} +\@writefile{lof}{\contentsline {figure}{\numberline {1}{\ignorespaces }}{1}{figure.1.1}} +\newlabel{fig:qgis000}{{1}{1}{QUANTUM GIS INTRODUCTION\relax }{figure.1.1}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {2}{\ignorespaces }}{1}{figure.1.2}} +\newlabel{fig:qgis001}{{2}{1}{QUANTUM GIS INTRODUCTION\relax }{figure.1.2}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {3}{\ignorespaces }}{1}{figure.1.3}} +\newlabel{fig:qgis002}{{3}{1}{QUANTUM GIS INTRODUCTION\relax }{figure.1.3}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {4}{\ignorespaces }}{2}{figure.1.4}} +\newlabel{fig:qgis003}{{4}{2}{QUANTUM GIS INTRODUCTION\relax }{figure.1.4}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {5}{\ignorespaces }}{2}{figure.1.5}} +\newlabel{fig:qgis004}{{5}{2}{QUANTUM GIS INTRODUCTION\relax }{figure.1.5}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {6}{\ignorespaces }}{2}{figure.1.6}} +\newlabel{fig:qgis005}{{6}{2}{QUANTUM GIS INTRODUCTION\relax }{figure.1.6}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {7}{\ignorespaces }}{2}{figure.1.7}} +\newlabel{fig:qgis006}{{7}{2}{QUANTUM GIS INTRODUCTION\relax }{figure.1.7}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {8}{\ignorespaces }}{2}{figure.1.8}} +\newlabel{fig:qgis007}{{8}{2}{QUANTUM GIS INTRODUCTION\relax }{figure.1.8}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {9}{\ignorespaces }}{2}{figure.1.9}} +\newlabel{fig:qgis008}{{9}{2}{QUANTUM GIS INTRODUCTION\relax }{figure.1.9}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {10}{\ignorespaces }}{3}{figure.1.10}} +\newlabel{fig:qgis009}{{10}{3}{QUANTUM GIS INTRODUCTION\relax }{figure.1.10}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {11}{\ignorespaces }}{3}{figure.1.11}} +\newlabel{fig:qgis010}{{11}{3}{QUANTUM GIS INTRODUCTION\relax }{figure.1.11}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {12}{\ignorespaces }}{3}{figure.1.12}} +\newlabel{fig:qgis011}{{12}{3}{QUANTUM GIS INTRODUCTION\relax }{figure.1.12}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {13}{\ignorespaces }}{3}{figure.1.13}} +\newlabel{fig:qgis012}{{13}{3}{QUANTUM GIS INTRODUCTION\relax }{figure.1.13}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {14}{\ignorespaces }}{3}{figure.1.14}} +\newlabel{fig:qgis013}{{14}{3}{QUANTUM GIS INTRODUCTION\relax }{figure.1.14}{}} +\@writefile{toc}{\contentsline {section}{QUANTUM GIS GRASS PLUGIN}{3}{section*.4}} +\@writefile{lof}{\contentsline {figure}{\numberline {15}{\ignorespaces }}{3}{figure.1.15}} +\newlabel{fig:qgis014}{{15}{3}{QUANTUM GIS GRASS PLUGIN\relax }{figure.1.15}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {16}{\ignorespaces }}{3}{figure.1.16}} +\newlabel{fig:qgis015}{{16}{3}{QUANTUM GIS GRASS PLUGIN\relax }{figure.1.16}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {17}{\ignorespaces }}{4}{figure.1.17}} +\newlabel{fig:qgis016}{{17}{4}{QUANTUM GIS GRASS PLUGIN\relax }{figure.1.17}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {18}{\ignorespaces }}{4}{figure.1.18}} +\newlabel{fig:qgis0017}{{18}{4}{QUANTUM GIS GRASS PLUGIN\relax }{figure.1.18}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {19}{\ignorespaces }}{4}{figure.1.19}} +\newlabel{fig:qgis018}{{19}{4}{QUANTUM GIS GRASS PLUGIN\relax }{figure.1.19}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {20}{\ignorespaces }}{4}{figure.1.20}} +\newlabel{fig:qgis019}{{20}{4}{QUANTUM GIS GRASS PLUGIN\relax }{figure.1.20}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {21}{\ignorespaces }}{4}{figure.1.21}} +\newlabel{fig:qgis020}{{21}{4}{QUANTUM GIS GRASS PLUGIN\relax }{figure.1.21}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {22}{\ignorespaces }}{4}{figure.1.22}} +\newlabel{fig:qgis021}{{22}{4}{QUANTUM GIS GRASS PLUGIN\relax }{figure.1.22}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {23}{\ignorespaces }}{4}{figure.1.23}} +\newlabel{fig:qgis022}{{23}{4}{QUANTUM GIS GRASS PLUGIN\relax }{figure.1.23}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {24}{\ignorespaces }}{5}{figure.1.24}} +\newlabel{fig:qgis023}{{24}{5}{QUANTUM GIS GRASS PLUGIN\relax }{figure.1.24}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {25}{\ignorespaces }}{5}{figure.1.25}} +\newlabel{fig:qgis024}{{25}{5}{QUANTUM GIS GRASS PLUGIN\relax }{figure.1.25}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {26}{\ignorespaces }}{5}{figure.1.26}} +\newlabel{fig:qgis025}{{26}{5}{QUANTUM GIS GRASS PLUGIN\relax }{figure.1.26}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {27}{\ignorespaces }}{5}{figure.1.27}} +\newlabel{fig:qgis026}{{27}{5}{QUANTUM GIS GRASS PLUGIN\relax }{figure.1.27}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {28}{\ignorespaces }}{5}{figure.1.28}} +\newlabel{fig:qgis027}{{28}{5}{QUANTUM GIS GRASS PLUGIN\relax }{figure.1.28}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {29}{\ignorespaces }}{5}{figure.1.29}} +\newlabel{fig:qgis028}{{29}{5}{QUANTUM GIS GRASS PLUGIN\relax }{figure.1.29}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {30}{\ignorespaces }}{5}{figure.1.30}} +\newlabel{fig:qgis029}{{30}{5}{QUANTUM GIS GRASS PLUGIN\relax }{figure.1.30}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {31}{\ignorespaces }}{5}{figure.1.31}} +\newlabel{fig:qgis030}{{31}{5}{QUANTUM GIS GRASS PLUGIN\relax }{figure.1.31}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {32}{\ignorespaces }}{6}{figure.1.32}} +\newlabel{fig:qgis031}{{32}{6}{QUANTUM GIS GRASS PLUGIN\relax }{figure.1.32}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {33}{\ignorespaces }}{6}{figure.1.33}} +\newlabel{fig:qgis032}{{33}{6}{QUANTUM GIS GRASS PLUGIN\relax }{figure.1.33}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {34}{\ignorespaces }}{6}{figure.1.34}} +\newlabel{fig:qgis033}{{34}{6}{QUANTUM GIS GRASS PLUGIN\relax }{figure.1.34}{}} +\@writefile{toc}{\contentsline {subsection}{GRASS PLUGIN PROCESSING}{6}{section*.5}} +\@writefile{lof}{\contentsline {figure}{\numberline {35}{\ignorespaces }}{6}{figure.1.35}} +\newlabel{fig:qgis034}{{35}{6}{GRASS PLUGIN PROCESSING\relax }{figure.1.35}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {36}{\ignorespaces }}{7}{figure.1.36}} +\newlabel{fig:qgis035}{{36}{7}{GRASS PLUGIN PROCESSING\relax }{figure.1.36}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {37}{\ignorespaces }}{7}{figure.1.37}} +\newlabel{fig:qgis036}{{37}{7}{GRASS PLUGIN PROCESSING\relax }{figure.1.37}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {38}{\ignorespaces }}{7}{figure.1.38}} +\newlabel{fig:qgis037}{{38}{7}{GRASS PLUGIN PROCESSING\relax }{figure.1.38}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {39}{\ignorespaces }}{7}{figure.1.39}} +\newlabel{fig:qgis038}{{39}{7}{GRASS PLUGIN PROCESSING\relax }{figure.1.39}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {40}{\ignorespaces }}{7}{figure.1.40}} +\newlabel{fig:qgis039}{{40}{7}{GRASS PLUGIN PROCESSING\relax }{figure.1.40}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {41}{\ignorespaces }}{7}{figure.1.41}} +\newlabel{fig:qgis040}{{41}{7}{GRASS PLUGIN PROCESSING\relax }{figure.1.41}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {42}{\ignorespaces }}{8}{figure.1.42}} +\newlabel{fig:qgis041}{{42}{8}{GRASS PLUGIN PROCESSING\relax }{figure.1.42}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {43}{\ignorespaces }}{8}{figure.1.43}} +\newlabel{fig:qgis042}{{43}{8}{GRASS PLUGIN PROCESSING\relax }{figure.1.43}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {44}{\ignorespaces }}{8}{figure.1.44}} +\newlabel{fig:qgis043}{{44}{8}{GRASS PLUGIN PROCESSING\relax }{figure.1.44}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {45}{\ignorespaces }}{8}{figure.1.45}} +\newlabel{fig:qgis044}{{45}{8}{GRASS PLUGIN PROCESSING\relax }{figure.1.45}{}} +\@writefile{toc}{\contentsline {chapter}{Starter Manual to GRASS GIS}{9}{section*.6}} +\@writefile{toc}{\contentsline {section}{Introduction}{9}{section*.7}} +\@writefile{lof}{\contentsline {figure}{\numberline {1}{\ignorespaces Windows Native GRASS}}{9}{figure.2.1}} +\newlabel{fig:grass000}{{1}{9}{Introduction\relax }{figure.2.1}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {2}{\ignorespaces Welcome Screen}}{9}{figure.2.2}} +\newlabel{fig:grass001}{{2}{9}{Introduction\relax }{figure.2.2}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {3}{\ignorespaces GIS Manager}}{9}{figure.2.3}} +\newlabel{fig:grass002}{{3}{9}{Introduction\relax }{figure.2.3}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {4}{\ignorespaces Basic Display}}{9}{figure.2.4}} +\newlabel{fig:grass003}{{4}{9}{Introduction\relax }{figure.2.4}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {5}{\ignorespaces }}{9}{figure.2.5}} +\newlabel{fig:grass004}{{5}{9}{Introduction\relax }{figure.2.5}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {6}{\ignorespaces }}{9}{figure.2.6}} +\newlabel{fig:grass005}{{6}{9}{Introduction\relax }{figure.2.6}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {7}{\ignorespaces }}{10}{figure.2.7}} +\newlabel{fig:grass006}{{7}{10}{Introduction\relax }{figure.2.7}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {8}{\ignorespaces }}{10}{figure.2.8}} +\newlabel{fig:grass007}{{8}{10}{Introduction\relax }{figure.2.8}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {9}{\ignorespaces }}{10}{figure.2.9}} +\newlabel{fig:grass008}{{9}{10}{Introduction\relax }{figure.2.9}{}} +\@writefile{toc}{\contentsline {section}{DEM MANIPULATIONS}{10}{section*.8}} +\@writefile{lof}{\contentsline {figure}{\numberline {10}{\ignorespaces Display a DEM}}{10}{figure.2.10}} +\newlabel{fig:grass009}{{10}{10}{DEM MANIPULATIONS\relax }{figure.2.10}{}} +\@writefile{toc}{\contentsline {subsection}{Compute slope and aspect}{10}{section*.9}} +\@writefile{lof}{\contentsline {figure}{\numberline {11}{\ignorespaces Slope}}{10}{figure.2.11}} +\newlabel{fig:grass010}{{11}{10}{Compute slope and aspect\relax }{figure.2.11}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {12}{\ignorespaces Shaded Relief}}{11}{figure.2.12}} +\newlabel{fig:grass011}{{12}{11}{Compute slope and aspect\relax }{figure.2.12}{}} +\@writefile{toc}{\contentsline {subsection}{Watershed Basin Analysis Program}{11}{section*.10}} +\@writefile{lof}{\contentsline {figure}{\numberline {13}{\ignorespaces Generated Basins}}{11}{figure.2.13}} +\newlabel{fig:grass012}{{13}{11}{Watershed Basin Analysis Program\relax }{figure.2.13}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {14}{\ignorespaces Generated streams}}{11}{figure.2.14}} +\newlabel{fig:grass013}{{14}{11}{Watershed Basin Analysis Program\relax }{figure.2.14}{}} +\@writefile{toc}{\contentsline {subsection}{Stream Pollution Monitoring Station site identification}{11}{section*.11}} +\@writefile{lof}{\contentsline {figure}{\numberline {15}{\ignorespaces }}{12}{figure.2.15}} +\newlabel{fig:grass014}{{15}{12}{Stream Pollution Monitoring Station site identification\relax }{figure.2.15}{}} +\@writefile{toc}{\contentsline {section}{GRASS GIS Habitat Analysis}{12}{section*.12}} +\@writefile{toc}{\contentsline {subsection}{Introduction}{12}{section*.13}} +\@writefile{lof}{\contentsline {figure}{\numberline {16}{\ignorespaces BUFFERING}}{12}{figure.2.16}} +\newlabel{fig:grass015}{{16}{12}{Introduction\relax }{figure.2.16}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {17}{\ignorespaces MAP CALCULATOR}}{12}{figure.2.17}} +\newlabel{fig:grass016}{{17}{12}{Introduction\relax }{figure.2.17}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {18}{\ignorespaces Query Map with mouse}}{12}{figure.2.18}} +\newlabel{fig:grass017}{{18}{12}{Introduction\relax }{figure.2.18}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {19}{\ignorespaces Launch New Display}}{12}{figure.2.19}} +\newlabel{fig:grass018}{{19}{12}{Introduction\relax }{figure.2.19}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {20}{\ignorespaces Erase Display Icon}}{13}{figure.2.20}} +\newlabel{fig:grass019}{{20}{13}{Introduction\relax }{figure.2.20}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {21}{\ignorespaces Redraw Icon}}{13}{figure.2.21}} +\newlabel{fig:grass020}{{21}{13}{Introduction\relax }{figure.2.21}{}} +\@writefile{toc}{\contentsline {subsection}{HABITAT PRESERVATION MISSION}{13}{section*.14}} +\@writefile{toc}{\contentsline {subsection}{HABITAT SCORING SYSTEM (from Fish and Wildlife Service) }{13}{section*.15}} +\@writefile{toc}{\contentsline {subsection}{PROCESSING THE SCORING MAPS}{13}{section*.16}} +\@writefile{toc}{\contentsline {subsection}{HABITAT SCORING SCRIPT}{13}{section*.17}} +\@writefile{toc}{\contentsline {subsection}{FINALIZING THE SCORING MAP}{14}{section*.18}} +\@writefile{lof}{\contentsline {figure}{\numberline {22}{\ignorespaces Scoring Map}}{14}{figure.2.22}} +\newlabel{fig:grass021}{{22}{14}{FINALIZING THE SCORING MAP\relax }{figure.2.22}{}} +\@writefile{toc}{\contentsline {subsection}{CLUMPING SUITABLE AREAS}{14}{section*.19}} +\@writefile{lof}{\contentsline {figure}{\numberline {23}{\ignorespaces Clumped map}}{14}{figure.2.23}} +\newlabel{fig:grass022}{{23}{14}{CLUMPING SUITABLE AREAS\relax }{figure.2.23}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {24}{\ignorespaces Seleceted Habitat Areas}}{15}{figure.2.24}} +\newlabel{fig:grass023}{{24}{15}{CLUMPING SUITABLE AREAS\relax }{figure.2.24}{}} +\@writefile{toc}{\contentsline {subsection}{EXPORT RESULTS TO VECTOR}{15}{section*.20}} +\@writefile{lof}{\contentsline {figure}{\numberline {25}{\ignorespaces Vector Export}}{15}{figure.2.25}} +\newlabel{fig:grass024}{{25}{15}{EXPORT RESULTS TO VECTOR\relax }{figure.2.25}{}} +\@writefile{toc}{\contentsline {subsection}{Appendix A: Overview of Grass GUI}{16}{section*.21}} +\newlabel{appendixA}{{2}{16}{Appendix A: Overview of Grass GUI\relax }{section*.21}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {26}{\ignorespaces GRASS Menus (1/3)}}{16}{figure.2.26}} +\newlabel{fig:grass025}{{26}{16}{Appendix A: Overview of Grass GUI\relax }{figure.2.26}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {27}{\ignorespaces GRASS Menus (2/3)}}{16}{figure.2.27}} +\newlabel{fig:grass026}{{27}{16}{Appendix A: Overview of Grass GUI\relax }{figure.2.27}{}} +\@writefile{lof}{\contentsline {figure}{\numberline {28}{\ignorespaces GRASS Menus (3/3)}}{16}{figure.2.28}} +\newlabel{fig:grass027}{{28}{16}{Appendix A: Overview of Grass GUI\relax }{figure.2.28}{}} +\@writefile{toc}{\contentsline {subsection}{Appendix B: GRASS SCRIPT}{17}{section*.22}} +\newlabel{appendixB}{{2}{17}{Appendix B: GRASS SCRIPT\relax }{section*.22}{}} Added: trunk/grassaddons/gipe/grass_starter/main_document.out =================================================================== --- trunk/grassaddons/gipe/grass_starter/main_document.out (rev 0) +++ trunk/grassaddons/gipe/grass_starter/main_document.out 2007-04-20 13:05:39 UTC (rev 534) @@ -0,0 +1,21 @@ +\BOOKMARK [0][-]{section*.2}{Starter Manual to Quantum GIS for GRASS GIS}{} +\BOOKMARK [1][-]{section*.3}{QUANTUM GIS INTRODUCTION}{section*.2} +\BOOKMARK [1][-]{section*.4}{QUANTUM GIS GRASS PLUGIN}{section*.2} +\BOOKMARK [2][-]{section*.5}{GRASS PLUGIN PROCESSING}{section*.4} +\BOOKMARK [0][-]{section*.6}{Starter Manual to GRASS GIS}{} +\BOOKMARK [1][-]{section*.7}{Introduction}{section*.6} +\BOOKMARK [1][-]{section*.8}{DEM MANIPULATIONS}{section*.6} +\BOOKMARK [2][-]{section*.9}{Compute slope and aspect}{section*.8} +\BOOKMARK [2][-]{section*.10}{Watershed Basin Analysis Program}{section*.8} +\BOOKMARK [2][-]{section*.11}{Stream Pollution Monitoring Station site identification}{section*.8} +\BOOKMARK [1][-]{section*.12}{GRASS GIS Habitat Analysis}{section*.6} +\BOOKMARK [2][-]{section*.13}{Introduction}{section*.12} +\BOOKMARK [2][-]{section*.14}{HABITAT PRESERVATION MISSION}{section*.12} +\BOOKMARK [2][-]{section*.15}{HABITAT SCORING SYSTEM \(from Fish and Wildlife Service\) }{section*.12} +\BOOKMARK [2][-]{section*.16}{PROCESSING THE SCORING MAPS}{section*.12} +\BOOKMARK [2][-]{section*.17}{HABITAT SCORING SCRIPT}{section*.12} +\BOOKMARK [2][-]{section*.18}{FINALIZING THE SCORING MAP}{section*.12} +\BOOKMARK [2][-]{section*.19}{CLUMPING SUITABLE AREAS}{section*.12} +\BOOKMARK [2][-]{section*.20}{EXPORT RESULTS TO VECTOR}{section*.12} +\BOOKMARK [2][-]{section*.21}{Appendix A: Overview of Grass GUI}{section*.12} +\BOOKMARK [2][-]{section*.22}{Appendix B: GRASS SCRIPT}{section*.12} Added: trunk/grassaddons/gipe/grass_starter/main_document.tex =================================================================== --- trunk/grassaddons/gipe/grass_starter/main_document.tex (rev 0) +++ trunk/grassaddons/gipe/grass_starter/main_document.tex 2007-04-20 13:05:39 UTC (rev 534) @@ -0,0 +1,49 @@ +% Main document GRASS Tutorials +% Language: Latex +% +% DON'T EDIT THIS FILE! + +\documentclass[a4paper]{report} + +% for boot +\usepackage{amssymb,amsmath} +% Note: we currently need to include amsmath before GRASSnews, because the +% latter redefines the equation environment ... +\usepackage{GRASSnews} +\usepackage[round]{natbib} +% for Sweave +\usepackage{listings} +% for tcltk-update +\usepackage{shortvrb} +%\usepackage{chapterbib} + + +\sloppy{} + + +\begin{document} + +\volume{1} +\volnumber{1} +\date{June 2007} +\titlepage + + + +\begin{article} + \input{article_QGIS} +\end{article} + +\newpage + +\begin{article} + \input{article_GRASS} +\end{article} + + +\end{document} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: main_document.tex +%%% End: Added: trunk/grassaddons/gipe/grass_starter/main_document.toc =================================================================== --- trunk/grassaddons/gipe/grass_starter/main_document.toc (rev 0) +++ trunk/grassaddons/gipe/grass_starter/main_document.toc 2007-04-20 13:05:39 UTC (rev 534) @@ -0,0 +1,21 @@ +\contentsline {chapter}{Starter Manual to Quantum GIS for GRASS GIS}{1}{section*.2} +\contentsline {section}{QUANTUM GIS INTRODUCTION}{1}{section*.3} +\contentsline {section}{QUANTUM GIS GRASS PLUGIN}{3}{section*.4} +\contentsline {subsection}{GRASS PLUGIN PROCESSING}{6}{section*.5} +\contentsline {chapter}{Starter Manual to GRASS GIS}{9}{section*.6} +\contentsline {section}{Introduction}{9}{section*.7} +\contentsline {section}{DEM MANIPULATIONS}{10}{section*.8} +\contentsline {subsection}{Compute slope and aspect}{10}{section*.9} +\contentsline {subsection}{Watershed Basin Analysis Program}{11}{section*.10} +\contentsline {subsection}{Stream Pollution Monitoring Station site identification}{11}{section*.11} +\contentsline {section}{GRASS GIS Habitat Analysis}{12}{section*.12} +\contentsline {subsection}{Introduction}{12}{section*.13} +\contentsline {subsection}{HABITAT PRESERVATION MISSION}{13}{section*.14} +\contentsline {subsection}{HABITAT SCORING SYSTEM (from Fish and Wildlife Service) }{13}{section*.15} +\contentsline {subsection}{PROCESSING THE SCORING MAPS}{13}{section*.16} +\contentsline {subsection}{HABITAT SCORING SCRIPT}{13}{section*.17} +\contentsline {subsection}{FINALIZING THE SCORING MAP}{14}{section*.18} +\contentsline {subsection}{CLUMPING SUITABLE AREAS}{14}{section*.19} +\contentsline {subsection}{EXPORT RESULTS TO VECTOR}{15}{section*.20} +\contentsline {subsection}{Appendix A: Overview of Grass GUI}{16}{section*.21} +\contentsline {subsection}{Appendix B: GRASS SCRIPT}{17}{section*.22} Added: trunk/grassaddons/gipe/grass_starter/qgis000.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis000.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis001.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis001.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis002.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis002.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis003.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis003.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis004.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis004.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis005.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis005.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis006.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis006.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis007.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis007.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis008.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis008.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis009.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis009.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis010.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis010.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis011.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis011.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis012.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis012.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis013.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis013.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis014.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis014.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis015.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis015.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis016.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis016.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis017.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis017.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis018.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis018.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis019.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis019.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis020.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis020.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis021.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis021.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis022.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis022.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis023.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis023.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis024.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis024.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis025.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis025.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis026.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis026.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis027.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis027.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis028.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis028.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis029.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis029.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis030.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis030.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis031.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis031.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis032.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis032.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis033.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis033.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis034.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis034.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis035.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis035.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis036.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis036.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis037.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis037.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis038.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis038.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis039.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis039.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis040.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis040.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis041.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis041.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis042.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis042.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis043.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis043.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Added: trunk/grassaddons/gipe/grass_starter/qgis044.png =================================================================== (Binary files differ) Property changes on: trunk/grassaddons/gipe/grass_starter/qgis044.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream From chemin at grass.itc.it Fri Apr 20 15:33:13 2007 From: chemin at grass.itc.it (chemin@grass.itc.it) Date: Fri Apr 20 15:39:51 2007 Subject: [grass-addons] r535 - trunk/grassaddons/gipe/grass_starter Message-ID: <200704201333.l3KDXDUd027334@grass.itc.it> Author: chemin Date: 2007-04-20 15:29:40 +0200 (Fri, 20 Apr 2007) New Revision: 535 Added: trunk/grassaddons/gipe/grass_starter/main_document.pdf Log: Added PDF version of grass_starter (main_document.pdf) for the WIKI download Added: trunk/grassaddons/gipe/grass_starter/main_document.pdf =================================================================== --- trunk/grassaddons/gipe/grass_starter/main_document.pdf (rev 0) +++ trunk/grassaddons/gipe/grass_starter/main_document.pdf 2007-04-20 13:29:40 UTC (rev 535) @@ -0,0 +1,30247 @@ +%PDF-1.4 +5 0 obj +<< /S /GoTo /D (section*.2) >> +endobj +8 0 obj +(Starter Manual to Quantum GIS for GRASS GIS) +endobj +9 0 obj +<< /S /GoTo /D (section*.3) >> +endobj +12 0 obj +(QUANTUM GIS INTRODUCTION) +endobj +13 0 obj +<< /S /GoTo /D (section*.4) >> +endobj +16 0 obj +(QUANTUM GIS GRASS PLUGIN) +endobj +17 0 obj +<< /S /GoTo /D (section*.5) >> +endobj +20 0 obj +(GRASS PLUGIN PROCESSING) +endobj +21 0 obj +<< /S /GoTo /D (section*.6) >> +endobj +24 0 obj +(Starter Manual to GRASS GIS) +endobj +25 0 obj +<< /S /GoTo /D (section*.7) >> +endobj +28 0 obj +(Introduction) +endobj +29 0 obj +<< /S /GoTo /D (section*.8) >> +endobj +32 0 obj +(DEM MANIPULATIONS) +endobj +33 0 obj +<< /S /GoTo /D (section*.9) >> +endobj +36 0 obj +(Compute slope and aspect) +endobj +37 0 obj +<< /S /GoTo /D (section*.10) >> +endobj +40 0 obj +(Watershed Basin Analysis Program) +endobj +41 0 obj +<< /S /GoTo /D (section*.11) >> +endobj +44 0 obj +(Stream Pollution Monitoring Station site identification) +endobj +45 0 obj +<< /S /GoTo /D (section*.12) >> +endobj +48 0 obj +(GRASS GIS Habitat Analysis) +endobj +49 0 obj +<< /S /GoTo /D (section*.13) >> +endobj +52 0 obj +(Introduction) +endobj +53 0 obj +<< /S /GoTo /D (section*.14) >> +endobj +56 0 obj +(HABITAT PRESERVATION MISSION) +endobj +57 0 obj +<< /S /GoTo /D (section*.15) >> +endobj +60 0 obj +(HABITAT SCORING SYSTEM \(from Fish and Wildlife Service\) ) +endobj +61 0 obj +<< /S /GoTo /D (section*.16) >> +endobj +64 0 obj +(PROCESSING THE SCORING MAPS) +endobj +65 0 obj +<< /S /GoTo /D (section*.17) >> +endobj +68 0 obj +(HABITAT SCORING SCRIPT) +endobj +69 0 obj +<< /S /GoTo /D (section*.18) >> +endobj +72 0 obj +(FINALIZING THE SCORING MAP) +endobj +73 0 obj +<< /S /GoTo /D (section*.19) >> +endobj +76 0 obj +(CLUMPING SUITABLE AREAS) +endobj +77 0 obj +<< /S /GoTo /D (section*.20) >> +endobj +80 0 obj +(EXPORT RESULTS TO VECTOR) +endobj +81 0 obj +<< /S /GoTo /D (section*.21) >> +endobj +84 0 obj +(Appendix A: Overview of Grass GUI) +endobj +85 0 obj +<< /S /GoTo /D (section*.22) >> +endobj +88 0 obj +(Appendix B: GRASS SCRIPT) +endobj +89 0 obj +<< /S /GoTo /D [90 0 R /Fit ] >> +endobj +97 0 obj << +/Length 1366 +/Filter /FlateDecode +>> +stream +xڥXo8 ~_GUQ~m݆؊.ٽ`nk̉iq(KvM{+,E]H8D!sN@baNN.V3\Du&V3Ԃ nhC-,`)pƍİ8îeRݾ1$,9bJ) +R7-~qg K"}qOLblJz~ђendstream +endobj +90 0 obj << +/Type /Page +/Contents 97 0 R +/Resources 96 0 R +/MediaBox [0 0 595.2756 841.8898] +/Parent 120 0 R +/Annots [ 110 0 R 112 0 R 114 0 R 116 0 R 118 0 R 119 0 R ] +>> endobj +91 0 obj << +/Type /XObject +/Subtype /Image +/Width 1057 +/Height 374 +/BitsPerComponent 8 +/Length 319974 +/ColorSpace /DeviceRGB +/Filter /DCTDecode +>> +stream + +  +#12A$B3Q%4Ra5q'CSb6Ddt + +MOu~-ڰ  wB֩9 +E$IE9ea QN >&.C{>=w(acdbK|sZؒy N0HeSK_3ļVHmV xO!Z_1϶+$!eRUiD6duuJ#P k0[`^f{^eߠ& nWI,.RED2ކcBMmEvn0F <5Wж^accVTԳfʓqd(e)ˋ(M܊y M/PVuqP8 +#tKyVbv~:PU|;wgf͊v5+^\Ӝvp\}dYVXh epr*7QFgF2{#Y%OӘbV+/! + +d6~KWku5(}aHkeof*ܶ+V<}Nm+?{0ꮭo**]ܹm,U_F>W["K ʥ5u+Eoy-jkypiī2(ӣ_yuf_\2N8w JLtiu\NdXlP깳4*JM1<Hf!r*SMf 6~YjT!EfUZ岾: +2괟toQ <~g(m[)V[3c<yٸȆH tk2s]\Ѵ'k(2,jSMPnI-MuG5)*ն.ފHeܥ~&q = ={'y/̿ ՂbQ^W2r +FBr*SũEAT3FndZBu߉HVFWN)uf_fPcśųNdWeDwuzMN]%qRti Qb +Z*;j{{V͈u + + !3Y(LElkv5o^i M?!M"*KmJլR +HS|qPM1qъŠM=Ez򱝂M?"JqDBp6,$M'bOm͜hҵԫi<~#m[r±AG1HG*ZrH٬Jjql^mc.d6wR07/dZ-PΉefUqQ:kW +B,EIijhV ]N'xѫO;JEZ3eXէ cRص3zbvUHs͹VN94_dQrd21qE9hU]d}\hm%6%^% K8Л,;>{6M߮Jme^z~Gĵċ[Xdb}9*v?i ̍pj%ѵ+ג[5EX#b ܝeh/M>nSqT*bmƅyEU:-Q3rjL,3<#Yj6ξ';{#Cf,fK?ꀬf2˭2. +r+`JBWJV݊V'i"܆hr7UN>ҕӓZt\ aE7cYQzQϺY_ptb5#Wb,n/+Zȴk܂H.SIi[ͅdicW/z% `0(mJ]\s⡭fjhhbk >L'>_-:z+, rޭr^,˾Qo + +˔^>vv3=hJoJ;5>2XWHo״ڢ֔ i;fZM^T*n33ɳrF_jW]kerղ ĥi+nc'豹&rkq}lUcn1vR&sj]iL1/57HT;{ '*Vnn.X:솨3f˪$TL9: OOL;g:N)d j'YSVbDVR6GV@s>8{=cj^JVz("h9\E\4199L|hPb/>y6ɭkV2kr }EgkiS&s +7&;Vror aX &×5 +Q +j͛ '{+䲁oPc!YC: " @6ȐMu`^ⷬ:! m)fVM6WPQSYU@]LrLp-.i@gr.ͭ4,%K@әHge/߀gRIHߓ~N8eizT٬ +{C{vQZM a&9rd:UK!;DXnce)uPR5VPZS"J P +ɩ>"kӃs,HVr{&uv^.Cr̬\Hez5}G3=:mbT2Nq/}zmRX3e6Zٓیmv f cMɛiLqԸ}0mvnR3?"/4tyaZ 1b_TڙB`Y SՔe?{Xyjtȗa5s\VbBe#5ʋrǾ.zzPj3l(B0}cf],9 +а4 +Uȁ#@9JsʞDZ[)kv*5.v{rjmLeVVLVsPѲB7Hi +{`mA(/M̒8QDqPj:ABA.^7 +ERlˇf}:NAU +n83%{}8mb) BZVW_lmurNZ[+R5+φ[Jk +zCs1zc-b,qh}_YJtm=𺏞o୿O]ܺߊŭr}`V cA5ԙJ^ds {k}!Gk@ψT{"7֮*ʢEc7Ave)7EGf9'_nU֜--j0aYV9LNN5 +f@Po`V|ܱ~{x=x7I2хo)>"ڐƏ㘙V;h&4;N$6QzL;DA8͆D]EWeWWݚNr}Cq;;YK --ejxq`ܔ~ 3; +~xvlX[Vڴݛr u춸/oHS•-xs\h^զ$OfɸVPdSG֖χC,/,.5bVo۽mu h]~Z˜}i.'%u'M$ @ +9 ZB^;ghqёSkQ1k)b7ٮ70_MnTPe}E^ͻJ\fr "/u~Q +ƛVS)5.- +9 +#x.&"8.31Dw? d_SX+(pabO C*2YMΒJoÓ(*[b ݹn~dzur2H"c2=}}-LqB&) +nmlT؁ +?vd*+'.#ʢD +Zq z)K=RO6gR9eHB@JdF`Ma(??I&rD$KL +y mɩ]AOƧ- 黵OA*XPQ.&:S#..AcVXԡK%ȷ.1  (O#}{K] +7Rѵ`< ,ΗP$Ȃ9{T5&ZJW~Φ+b(Q5R՜PE,@U 8<sr;(U!,Q7,Ge%OͺA|Ž'5&et!H +OT+#rB DE_RԦNfe +'bDna76'b%MiU]V2f k@vT@)V5[rf۫z{m?aBmH划y +B듞wa! T  C\:V.^o^,Yx?Q$@)ın࢔uMY&w A<߶b]S&hܰ}sePr +Rf@Fj\L_7Gn8$B47KG e&P5%~+!}+*?I8Px\$s5À$=gTj(l@F&rlzUzMm) SU*P)AUXRUj^eu'}< +Wq +8.|mT|IQĶȖ *G|xOe<ېQU;4~Awb +v'`Rv m:72V+ӣYlhHIKɀ3fQp2OH{r͝ugY}Ȩ RM#FDL.Zjmi9;*Z^b^5A"_+.W4*{"-A"2j}ZY5f\^v,_I(I)TgW +F"R2F]i +ĸ*E +M5٭6 zg"xWbϫ׹7F fARN(,XA1"lVV + ?㙈xDA~c= +zYL(&b#̈%%%2S>Fg?s1g?&9qę$SY< +b?"10313%2C>8^fb?nch.30!#<DL"uhz%#= +?p'sȏ~g +:BYLIILqq/ +1hB{:3笽WId[w!xu:&31?( +TC[9잙':kLN}sᇬD|A&W13)2du ~d&wgWZ1 $v(kPd0@:9Z&0*31Z~+ƕexVT^iM* rkdY n89(< +A]! 77 + +RAV P&8Qv;ImnE$Җ6,8I`Ltq1р6AI,f!-S . ;NF%Y/0 on (mo׮۰n-ߕϖW*)q'i|U?yg,ݠ_$a95&|z+)UƎU~p靉q an%Ր aۼUF6&d-k^> \Gh $#, +'݈P X1ڟ6e\UTSeK2N QA**\n, 3' oͼlHaMo͜Mnˎ^%ssN4[Y$5J. :\wU9%(x7וݨ4 [Uu6KXOhuSVk nZ6Gmqo:*YZ6ʬRm3hmU $%ERkY_ ]=$fWl +` +Of=I%]w +.b i˛I3[ (IhBkr+=z[BGzJ aQIa +X;/AX;[*13,%\@ )3S˯#YW,[Q6eh rI]@Wb '-vuߪ14Y[]b$A# ֒d+}a]m *B 2(?]1;N!]6UWgPxy̰`7lg]l-ҕV udWHo|b 6, +_&Juww=ŪtcI>t3#0Șٴ$HIY[m]̀ wQeՕna1晿iS'huB4NKl H){H%; MFW__PGT&AH2+ ( +F<$q?%okBuȊ~d>̾TrG#"uZ3﷭ո+`S,hdmLŎ ]*r^6`` _Nd:G3 ANeoA_IUw+Q13]Dee+^y9E v1A/RKg$Dzć| IpOU2r9!E⥉n,H'~Jtī 漷>>܎v¯O_fC;v-"kTcBk uIJÅURgP)Lv{^VdOWՏ: 5<ɕri"&m"xoZejʒ Lb*['6 e5VaD2 +R^& dTBU%.:~g*Z%VC!w(Ő(;9@bwBB<#@w@ +:#?nOh/dJd^߬D~9fm#p6 mă`X + +$&}I +xFJ;I0:އOQWꮽN +% +L|ON9M l +μY-uEC q]Q{*CU]B8 +'tج W(l ep~~c { S"ڬu*ȩh>IX@IO TF`xDb~?=``DG?"bJ?H)(:DY3W9q9*YTr +bd^$kQc'%RGY4G\@4'2s{uaD P2 'Ș~"\.cG&eP6 +(_H}d"%c)޼O><-&|C?2%%$L QOG}f8n}i+^-7 C.g.$bf`@$՘ +^т9RF)N$\n%{wrB 330BSeQJ0Mar +|7mϏmطbHRf;)H;6Ͱ&?䧏߀̀=b&b"c>$32_L&L$fJ"D8)`g=fz#J8.Hs31>`&fxfd`~S< %mx|-a +zq3<@O11<?10"&]DFG&c%gfz~Hcq1#GHL~b8Db(iğ&"!&g +Ťi] +l +@b"dcQ"R]9(PQ舟.o +C;[45NK˗m6 38[;=N=X}G=G=G=G=G=G=G=G!!i;=-׿A5mdli}1}ʽW4n +['J1)@5 C%S=v'CO5Gٮ~I,kj"oK-T +9Q ;66RN"VmJa]\%)m?O5,V4~B՛,2p77)1U&k(PȾG( ,E8^)vRYͷ'*(؆#ֆ:JJٿig[wR˫X)`w!Kmf }mqoۨrYiiM2ܢ1mZ>Z)pDD$!k)>6Xn?dpmx^l\I-cH~WXuBvQ#N1ר,0WbCř5Sk },f +C4X%B.K=pzsvMe--M(]ksVȯ`Rs^Z 36t"=)-Zȡjb[+  #+1/!I]Gkj>IjDOBț38 Iw3?t\m]V'I̵ &s=VP!Bt +`B +p<JF9?}ޕyI3Y['Zyۯ9xs2SOOy.\u;qԽsg"$RQDȮBfQbJb:?< Au%<i' +,aSfAfXI$O@k-mҴGUL10``K[&1mʣQ +" ] ifV};g`JG +F #mo6UR>`(!u>DGFK'LH? 8Yfb"J;qȗYB#ֲJ\s2A`EI)BP#01zsWF U|a1'e܀bbffA2]b +Iz^.Ze"`&N9%P"M).% L,J!LS6V [$~$;|%0HBC%+U2GdECGT +)7 3:YlU +]`0nJ RLdI/+ApBa; GMؾ쫰#!7Q|L(="Ww}Lņfrk( rL +# Rxb*U%~𢐖Oq?̇좞µJ^BXdGH9QkL~ "0Ě_e +DGU 1 +E1 + +JVPTSHa6{VB;1Q4EK$ fVբ + +IJ]'ɞ'm"-řB;,Q]D˱(Fܻo檠(gtUdt'}O2P<G~< f#!H(C$$-qi %ɔ#d,H-'bLV,ul8{8I%Q[Q"l +8`xL*e_\F&$3QdƠqPZ,-Ayx9>H9X}*\JDD3&r a Xt` +TQSogWI% a9+E:H +IURt̰+T5.IEVv-PHB + +#8\2# Gh.Z#}?8OQ`װD~$b;ͅ]Uѕ*AA>ǍG?#13%qq#11\~'sV/i +7 +fCcG??߉ +~S>/W/M 1"EQP +SM2Һ+5͏ +*;)p +3@YX +vC,$ME87$2x){V>\Xb5Td;r'rY;*fbO&XO˴ޱj*ړCL}Gb){O ++XFG#Gv{{-S'4oQC)N֪Y|>`cSe{b&] +;OxA!)P#cJ8) +N{Ľ8(E +|((IP +Nx8nGČGYC@f: +9nRpQ>7ռsq=. x)(<0Qq% +,7YU+ + $o<ۓQߥFrc7?ǟ҇nG=2p' j c5~jb/e*/ciUjq+IQ^AJQZ:lLəjSZݪ8w0v@ +%q=B S1"]1&p; +HhH]:d eDڎ^lO$ +|(- xVvyn@+lbG˸y4v"!QP`βqc0iHqk-26,vejkk`[,[ا 1O a[rƁ-ÂԮ.Mb&XA`(\/ذ} 55]0,).R3\&1Rb>S(9CrN +ZLUȜKܦ%$dNJUWZG/#-K8 d4 +XpR!M H͏ah8&z2K_x`!.TQP$?2"P$1"@c +b''߭G_1R# + +7 J8<@$G8Ԩ xB;rv^b0Ab +xuPkI&(ZH%b|J*c$L!fH^妤 +_I NUN[yjg#LjB%k5e (/ F k{ c9=HT>!8%AO4xl3fYA +l"P ɟ혂N q)Q<] d+($b¸bR̜RmGřgx#è$?Ir'6l6kq_-p +L ¢d+.`r(2)Wy(3d-!8{JVj‹*A"7عv +@c8WRo +Ŷbw+$ d5>ž@B䈠$$_+8m;Lǽ  K "%+¸b\-)6(;`V Z"[0qغ0r16ھ"8ȖT3* _(zZdBɂ#"|ʸm@4wSarSn +1"@|ĔD+)BLIcY,s'!솙D!:P: +(,L~V$^@lO*)1CC% uq*Zq Ir-WRYH+:l $6r1*PXltf~;ABPj#"l@`T)`? ⣔L|{'SH`>!7*rT,$n^5XcRL5!4vdH<112]`FLDlD˴r123_Mer'yrVe(񟕬z*%Jۥp!X&@ +KKBZd&`3иbd 3H(y)D#?f %*Fw[; W +үf܊5Tň + +y +XEm0#d>㸩Iģɕ t1uHN_PV\Vb &DxXp$g&f:_& /٭Mմ19]BtC/*tU8%\d$U$l #՗[Y3j<4Xf?XYv[Hd+5=ϣBii湨46l .\n%yTӛN 1ǻ 0%utByw +d#Ѐ@V>UI<UO?;ۻڒmЬWVQD xdaBlHFG?Ďc;@8| +8Rw˖]Feb!cւ!YQAcʬKE/X}`)1 +;DDϊ${(t~P.6Nǐ'2`+/ ?s oY'Thc27X2fB,qxkXJ(ȑrznrGgjɮb%v}uT5 +$ ;\MECN,Rx_װvŃ9Ib!^ 6oD+&Ir2rȓ${X8)5O 8 \NͯD<^>WYO-AQdbFm'6mn|;C&,e.c Km >H`_5ș@;{ ؆i=eJbL]r! wPrr"P)c8Mah}xaxa+~o11$qف*WBS&bW3NB̲鐒ࠁ3]bg0 GN%:ҋ %HE +^9]ƛnrJ$-"WR "'%mT +wVa)%*9xVeQ0Q +7> +N:URhZ B +8y:'eMvy 4OjS` +QgE +_͐ #1e*Tc+PЬThBӥNjTZ+VP)* +f%jYW^=}CD+LVi:љ,jT1əVI;PEf +46$[_~eCn׺sXjgM43;^YjXKxXFݜJ j{V0[.2{)_7W +x)"ҹ̏dyDvs\LHRFf:؝ +?3'DےdgKOx[<צ8XAĔLD%H^kC=r@β+c@e2T$x`dfD)EԱdWb!^lqWDOX$i魭-7%mM+arbo-k14-UkiXc+#MIz?K`ChH'xzfQ@cUK2,ru=C,30Vq]Q~"?=Vo`c +Fy +D9l$F0pqز>f{HnsLs5b1P9TҶxExʥ[vұfb2ϪcюEfmGj +V*K{2X=QRϺ<[jk輱]]pֱVA$nn!? +i۩4h8 +O'r3.@ipRxX'x𔃰cqaQDxQ# +$sxӎZ;*`edc3lYALϖ$f& OMl52׬t1߹uEԧiwi*V֢ʀ)RgeQ &DXrP<w>y?M+C7yXi;- Ym +l(WJ2Ȇ yÜd8mZy`vo'.j}h^AFY8b;J$!#V߫&-ֆe +""Q2%2<q>LִZ03{@Gx8 +Ψ<=$FQ׫z0%]ceRJQ`'(h"; ppgRٙ36.;FᏐ78kzY.[EP$P 'ەK!N.YekHsh +7 *N3z(N۪1!TI +v^%ʱjA2d +X +CI_uHĢ;`dheYJҟ'RLs<LyȢ]bx"86UmD@BbpL̜(dsɶaKTV$PƩʋHİX.a,ɽ1qx :<؁͸T }UEd;)&&d'bT lXܽToY@eqIO(P0wR=3^[e2bXI 7U؂6 +yri6A`NPyal D+͆&LAc +勖-.jjҖlbKXaEiFh&DbdY 1lrZF 9ـ[mu; 5,Ъʬەăm,R8P_Me@2+Bdȃ) +;l|)Fۯ7!rɀȰ;n8 i~$qP + x +X*H%]C ȘE2ܬl[$JM @]QCQIf(l*bC5haWFZ/; +|}H^N 03f"2I`#悅S0`W$Ů$Zu^W&&́'䗉JX@A +3L1=};j@x@lP"C1\u#!\~!N¥y +YFWvtf^1!Z+ 1Mq^f mǮq +6S+-3]?aib,.UY.>gC_S`@Vs-L*b#t6 +ң`2k j֩DoUi/p=>)~Sl̤I4Չ<lEkl\E% }~(?$D(LĈ}.NQ]1II0%b +Thq&@L6όsk,XD2)˖"Lp]UeWzn<Ĝx nRɀZ#ubK\:DɑLDs'dRbf\gu^  JR)MzS(kLXGEnM:2e!Nkj >?G)>Mā͙f]bڵt>S.܃%p!Dv D5|9"JlPא㘅@rs%TL.Bc ˾8nG)m ` u`>>R15i>fW%# } k@ŇkV`LX+($ ı* ~بIʡVmCd+@Y  K3ʾv"Y`d`l'rBΪ9U?ȫK'GNZ21`e`P[&a + & +ܩG( *ԻV^7(*T`tI@y 5.>$4؀ADQ3kG02kY}o[z>1c]^R 9E 8c'Ȣd oS[p&PVP&/u)/ O3$+.H=s&6C\r܊;rc쨧m$x=HcგLVddQ{ckAq4z`).дDWEU/s&mdCq,I9~Sr +^?:kU[+#%bL'P2Yt\fR5!J%'!?Ik\εgb`jԁY +5j$  *Yi XSoܕl^'Jȳ#Uں2ҭ?]&00)^ 3m|n6dZt2:x]tHg@.~T$MK˒y۵aR*og̻1Ia(MJZׅoVʥA~cz=bz=Cz=Cz=Cz=Cz=?mzss}nf%c N.YEo}c9T=Sznmm)*HI!AJ; v*q%f +7?6 }$7$||i7gt +셪j[匼[# u.6F?H1K?C4'V!&ZҞrtM:oDڂrF.0x.>b}*,g#TooQnu }}Z od'1^Vc쾅y^*xu1ꕨcVE*4i!UiҧU@*U@-VB(B@ +rEi~9zd7"Z^d%5S|,kbxۜC<|3*b{j5L}#=%`Xvr/l5R0d +b_Q"z=[nڽ؍{QZ_6?'V,4:s#ԎLAY{ڷ_u۩V೛=7fvłz1B梲:%Ҵ= +1[G5@Jk 7Hj-:k tVXj=3(#lCk[ (r=VʓjSe*h؊E92ٺXR=uP QiM[4US>;WҴ(LuǕ oGpx0^%M2APߚRbHi^T ~umm5ǥ1]7ҊYT&-X +[$eAb40j\L@̔DY)GiET + +^.AVs3ے.3dzJ{~GDw99fd"fKGx^fy8f:&"gqq%f{30PQ3Ngߟ"bDb`bJ yb& +; ~8>#m6a +X!&$?!.RD;$ɗ6mtL[KT`DTN%`pŗ_UD|QyثcKu1˒`e>ǃ6e2 +^t6ͫ^a)Kvx rL T6 _١m+ndq_SNtt~ZjhNu<Κݟz:nmuG~ޛaFr{Zv/Ug7 ;ɲaھqeΙ97agT3aeTM1b'<G6^laE([Z!4Z?qY0{nzvӹ\2tpqܵWq3ۢW϶Λи|^:-:}f#Jic嗮ՀS!asYMvZj.'dS\VfB)PZMe6SS @+J$ܯp2)2n:jL4ƸPqEǍt 1HDL,^c)W:푒dp- "%#v~eeULgx8m_wݛ?W5&?Uc=!>'[f뙔۶YVYedc'~4e1X<K!NeB/hHfU,s#ϝ9#J$cSjMA!9k3*FW'vfJfLrE#Տ$j4ԽA뙥 YuRfULkZRG,NO&T/e, +ɶ?=I׬)9@70ϳ'(fۨy 9c߳zfF?J&??Q+Uuƶ:5yjIӡ~jBfSބd[x$rmmVu`!5e `c1"`c0BC1$31bQb(ubK9Qa3$$RLfc)Zw%R7kxM(ÀkTA~Dڙ%bZYtoSSqj Oݐe? r2Wby$J3TX()x"#s% (8B$d + !AH&X Yy[6XLVgFAJ +XSY*N2HzBeX^|SI@F6ZVUV U*XwWebTKu8d=#"o;}5*& +w&V?Uk~^D*[ kW JXR'3nk9i$ab"f +uV(Se02DJȹ W*N6YY)_LftϢ {NU5V\nl(ͷr9y 0%O 6YTxw"s2 Wiq +"y9a|J.Ȋ1|I5 LH1ҕmEdP'2X-!k"j0MHj.\fyy:zT6Ɖ<1?)C@<zg` +2jU.Ss[j\3oEc(]nPf|}LHxt$X +X.𫯍+J"Sba^VX#]={\% .~^)$,9)DAuiJ[ +`<2@\KXZqP)+/ gv\?"Yg)$Jy$Ͷ܃qs" l; 0ŀ.'A13 w(jY{J^ +ưQZe<@!-DBYI ID 1 2L-l׋!vP:n$<r tN;bC]ܑպ+Y,HJ %JRe,22Nfd ~fEwmA"qxb)8)OӼqђ!xW&APE0^F1#p`"*OW1 A&b&eĐ^SBH ;5ޖ 1-Te ɕU +`شTՔ +9U(Ȧ>_ RW +ʑJ FE0!#0}g@y9ia&[͠<0S + dց𳈞&R ^Q=Ӻi6j Y$ \.|L kN)mląF!c\bDW.rZ8$Df1udm}UԀxs-w="X ɸ (ԇ]ҸǍ +0K2,'NDyͩU +UQNtA35DCE&tjK^-x+[/kו(+=iL%.#%XD%nZ3 ;$#l!^aI]u*bzNc-6%:yhvg&.J +[e *YuB ̹dDK @kaS&y 3$]Ԧg> +b&o)gXjٴDMUT {e*.U^ +^`ϖFv-Ȼ$j9IY (#y0Ō3 D3WE3e]%~O:iD3?"+_ ( 6-g7ֹ_d"ILx%L4L XlL zR9.UAHALc>OR%"?L5wy+Ln'&̠7A uvsYa||mFf- ]Sj$Nul2Uqui$J +I"k]a!30ŵ*G`d9iVbLZҘN*P0`GLa ˦Ǎq[r!HV8uG<wl6CL)w\|\,jd.7%oK! +LTXY5Iu +əH( /\I$ +g5FMDFT)[2z 뗪lnH=}"( R2!b3zC8/%B&|IAC#$ʫWip@0ÆL%s`X?d01 eN͚Z貗xDdcFBlt![j"xab!5Lj H`XWS%$PeIPWN"Tb&ArS &B~,䬁i +Ԧ +I틅旿iU$ AjO`6[TE12S#;J\'1vūOL + ˵6l}:}Gi7m߸@ŷ41Ziw1e)E|1hއֲY +21K_Rþ+dc׹ [+7U@<V_}+9XA:TfajV<6>/ GeYq^'Y-6z$NJ[)Bpkaş8*O٥ a27*8z=Y;z=z=z=z=a[3Eu{rյv&x]3Rfrٚ?Tk:yRqqYU +y +.ЩaS=) GF[qEƞOjvvr9mCvb銾wխN˟Eg a{1^v=14sMbh`8`DcS׫TL̆JP@D̑U[LW.W~u{(T3"7jX +KxZ +sZ5gTpyԱ6f#<J;0q2?b|ބ <{3$+i,͸_]_]dI[n͙x%#f؍mnSAr Q)x!UYAmUop `m߸ 2%H?my)٠)TcdŁ6"b|$Lg# ]Y*\y}| )df +L #qK!HLL!j`dldBÞ Q 8Y #f{-.bkX&%d +Uw*Zղ)x)1jqC +\ڠI9M,&3*ByQ:a+<=O*ny>ubg6[0&c +j=3q-[/mKѼfK#wEZNӱ ~SL[ +}wճ\'ۑNMض'1f^aכ[q +806SǴPtV +#% +_Y$+&HI MʢWdYv +}6;=YrYQ$+ʄb }R#D@T" ƀnUsɖ:TYLBe>O|AD$ h'! Vm%+0&T @_vL @dnYS2eʏ"ܙqC%GVP*@ed&(#ۗkZʥq"Ppaֿ29 {O"U?`+ 5'̪r$ ݋%8<I83ak,SP W8:HäW+!Ҋέ].e@PE@Pr,;ZHPZoIYڮf\!90*"k- kB~[J&PF Qˑ1)6いX'VuUZ% +,H݃;s\'(1"+3e]+l=ej128vuiBD0 0ߘ!ۡȃ';B3LĝNȄebA4Ȇ+* +[ +k9z g>rQ&yUxDϑg'$J6`}2ZúM*3a*SH"R =$ްwA")ĩ!YrC:ߤ )*4QӋ F[qc2UZ啁A=_LkfUX#edX\}{Fʖi8)b$! 2kh 8D65%FG1`Vl~<`̤b 6Tq8ew + 3-Ի*ɕ9YR Ni͊ +|I"Y6]2d.u<W6H%q2-D yMkpqd*(w!N+! d1$Y໴dLCQGQe +2ע X!$3s.*ήGWŗI-Q +*{< +^@Nĭo>&$ΫbKDQ^5ՋZn#5P%k N|MiKJHJJ–iZ,p1 +g+<ɜHf]Պة y,Zk#=TxVtUs d4Q1Ep`7׮5&BDJL`"BP.%P]~nQ䫜t"+%x$wdKْ /)xqHW!Q†)g0;@$^J%\kĵ^)0(EF*UARDItmS&#.yrOy27-ȥϗi 8eQPXvhzh#:њ%ҎCȪ-| !`XjN +JFu;:W/$, +|f$S2"%> +۔: +}2ܲ/k,RjcG.H>2)eUV1+]c3ij[+#vH裓"[qgP,VJkXS+/30{ӢZad3)mql&wZ%GFiYP< jz\M=+U.cc +6[^ +djE*xۧ5 fM'하!IqH-IYR2t\!}Jcd[*0IJԚdUfz=W +jK9 geUKnumGnaZjzTڿi]䊾^%sM}Ox>uTU&;nOPP HvXR Z +L:s* S۪̀>rw.E*v?ŷpUn$nv;>$r~uv#viL+G-jeOXJ s,5H-^ȣ#VR[u>ލ%@>:obtW1}K:drrW8KYƖ:c)cS[[tz/dGWC{Xol3L~t|1>౨†'Qb2V5^raf*LO7x3x)k)wHj/TaUp w -͹vZ m +*vG$5f⛛}kTDŬb+ |2(9y_)XoW1E{ķ5Nf q0;]"),8aڡq +XW¬]2IUx]*TAWELOW$bb +g +Hd$PA1y'0R%%S<cq3$JFCOda3]zYAI!c\u,MOBjvMh8IHSYaQ}mnLi뺷BUmhe` {)]$YRӊ#BGb*8K{{|7|8'`v +&$ +8)dV9/ݍ۱m]#2YCZjUlqԫ>ja{a7f%d3nxcT)9U*AȵL(jT LS2`؎B2| S#8D +?\O"3'#3@v=[Mf;MSՙ>c>J3o#ᄊ>X[Ezjl= 1v9iJf +Dh؈z +7Umʧ1 cz+Z\0KPMGX,aQ F[ +ә1ȔQt||eɆ% a΁8 +)a zSnie=l&Ffffdpxfum9-At -6c0 1ȻRqX{s,b9 v8WrWAP6 +V'8ERH_]?ʾmrSQmSUvfʐ +_/w4?OZ0.=GwQ#fҟ)> =tcF+Rl}Ob2,pY)UpcsPfQ͹0D1%)O03%3 |O߄f,qvͮfj=3њ폈?RV9Mo|-'O>Hbl/m=EL.צkHOmDis¬wv- {Eg= 9-JUa,ݷA1vYLxِ T 5^{?maC5FY1{ksk;k%)LfPecUe >J}!KXa hеi+nEϵ]S +rV~ +\MS*mveH ]5*tRB)A&s q OuzPtpZiGZ>j>:bj~EUd!Ue&u.V/ +,q#|WvyP6_EPoߗh/cnrHď*xӘ)1Fw t֝64lTb/1?7j^d#;Y͊eKKWи8Ml~"iTTE:jׅوLLLLq0S/=𵞞rSҲ/VípJBt7v.g: R4dFbZjXi-g&lL[rL(¸T^K0硫t]m ~DQS2WfBTt'1WoUr/>6:&C]M~U=_mX؝(ZqKQsԎДph?D>?0s 0,iMCRNRk=w'M G]Ŋəu{bvre䥱N^٭Z:Cb۸zo=lJtU_4r( gcn.g~6F#Zbj`=ս>4LjzEۚbŻC:gv]QGa| ;wc0t鹵ZBsrFءWa+ʟ-"+d<뙨%<+@DbPS +R#dA[eش40fNx%ؾt@IB.]\{o ;),B61t2 +G؉P&)Jj0k[1t5`>h"B6|bPB +=kDdž`.`'pH`X-o(n]چO*!^]`ް +h;#K$p6kچRD)m? + %JpśXqn38ۏpNI +r^]fۑi ++KV >DMb4 +v_p,q!92 +ǀ7UW"u6sA65ĕ|![}.rfVU}(Ԃ`ƮXFQL-RE +GhMԮ 1@ +glnxLƨS@1e +D"IX]3V +s>:]e&äQ +R vNvCQC{Um@Bܖ<kIU>Pbݻ7Ym]F,^Lj +j'ƈwFۻAX:d8EQ[FrRB; +qUsoz=-ףz;~L٨O9溲iT|]K?ut,vCS>*yDqx쭼VrJp5%/X#vV-X3iȲgOͩ +Zc4`7GFI +raYhs{"-/yP6jew_t{b1\3kd +U^ն+]tBXXD +9)PEuW#|%LE#u1WMm~c璶%`cTcq)bbxEԹD"‚Rl0) +*6]Mu_#95'$S$aٜΨ;oT2G)W)Nn"L$QaȋoNm}ӷjWpɓ#Wis LK@Ji,Lr^l٘mΓj92#ةf8ޯ+s!Mu' +i(ZY _ [6ް1f͗bU6mA^UV+I)aToV\r Cbg켴+ģXcia䉂q8fwEJD D`Qf.-6 ;_m0 n7$nWZϵkZz-scTAԐ5rb:1mT*?4]?Q=(ػVcS@Zv,WQ(%]_3*QQVٿ !# % +:V^o f.,P=ԫmȐA,ېHmҾٶ8m7 *hY+5[7QŦ*0mWIZ KECAŋC3+H +~7Uk,5qqجz#=}CMu=8LO"6X + +c1BBlnP ,f/*c+]xr[e10108=cSliZ{GfǮ#8X'D2V!TQAQL`HK'>kvTPAkm;YjxF=nj5Z^Mܥ +_hؾt>vY̠n];3+P)+)V2lcbl{NѬ#[,7eYu^l% _!Q +xd" &?s%11bK s&S#Dq?̏?3i_M)28zG`;LLҪb<؃ o:;a?~7;o>}dv唈*"l"&$X΂Laˆ{=k`DEPD%"BTD0bBW1J%S]r)D +GAϓIB#=5qyJJyXuI +,O1@)Ĥk +vÀF.<HalcS-2)&ߚ݌n' ] [ZXj-7+Ri} Y8L5:1YՋF +-pt3|w/ְ͖&asjD7AaS%跣2xۗQ\~Y9<~SGk1UNvjd\]CiL`NQ{L2r'4AZ,GgVz,ytV z'FoXbp`cI/O3@hu[=5>>FJ,ۧ+OW۟ST~vdQULJ3K65[y)(Sʌɱ( TFޣiɌ Id)ұl<&$qH$ 22#%…kG4 A g|N?4jD|"y]a褴9BrA̓T(`bo٘ZA: +)(h1ׯ%09rr%X?ŁK?-ecىt XS,)7G$yVM&hN$ϗ0YE`GFD|q%̌&+<;[ +1awYe>2S% KǍ+Zwܣ~)EKJ."\ 9.©!k +2ȩ50n?BI3 ; +9I)#ͷT6d  +p+\P*˘2% +O6~l +WLĜhof s +Z&ITD|ZAf21TB0: 0|2B*_Φ4 +8yJZ6fWx_QHWǹ&-a-+qIwØd 1 \W/$.NL=S@]f2l(Ark\͸ +ۡ* +pe<(& + YW tIBY-i8Mp%<8a*KZ2rFzDĀ,,.E$] fV#W90i͗{0P +kR&ebj`U5¹%+ IvApTvn$صHbR)Bs%9ٕ`wSMѪm֒ +s2 d̰#?Ӓyy!%dmrәuaVY["^*8U]RLtLުKZ<⸇ 0%B,̇3#+K0(4ޡb7 %@0bH$ q2L'BdGdLq0nʼA3@^D!l8NobdpBB + $N![ G؅ĕ JF)Sdz ^94Qq|1̵WsXG'YH<@QUʛ~5ʊ SOuϟ.j-3],,= +FFĊ# t춖&-y^UZٙ% 88K oZAY + +kM +ڲR͉<0%T  v]7FSY =9Q[ՔE:ߎmVU۹jt"p M#y5X +ڎyL/Mih=\In9%(D╒*jy@KF\m*xJկJ{*R#MgK +'Jݑ{_dm"nهS !Z/Ɓ`ֺ!33 #0, +FJ<_@GNf&{v~sG𣳕7}5lSIaΒ~7 SXqO֤ꌒ[M7*d]z=NtW:q3*:YLgw%یՈ}V.jQR1Y'Er,< n/"B_Kjk/lGX밴J?CjgURjVRr*]z@)"#"3"3"")""H> +]d%#y?U,Gc??DO#G|3 +vDR +Ra!&\&s=Mjq ][,eR엘lc!p""`&=9 jiNV[]'B`@!`sA +{$ +`R;Xv yPs'̷֯OgJj,nc+lu_zSJa6 8F3dn1_UGd>Y7O JJ"Tnj)׿2͊+4^NO|У6b˲nTN {ŢiԴl[Os1uwu)<\MK''$\zHK 3Y~uL`PIQ +%9Y;/)@Gv]A:N NMCJSj~[5wW* &;ث 8Fn6j3Jn~((th};"WڽY eUa-J oH[<[װF]7WWmέ;kUig70G1*VBgO?sSz+F q:%j!W.MWӗQzLogJ2_\6VObp~ݧ޵V[ݳf +j3Z+׬io6nnW-vFGSFrW5'#ܾOcjLgf_LɠxR-Li#5r]|N_!X+*YbM%ɯqBaanr] V`ТfUaa5R¹gpaY *W[#LV ZYLM\iYW+p xaIύ*0aDʌP:qS^hDI IA [dE+݅\y"!5gA2b T<{B|^C1Ɲ ^f|p ݏ +{u)zA,hR +IJJ7)c1Aq6{v"H Hp& !LP g˄^̑60&`AG/B:LtW$A(ʡG.]9*OIZȹa+AQ+!jM)|) :9ḑc ry;!r]>Xrkxvi;#67ɜ}Шw}WX{@`IuBuȐ,cՕj( +\kXcedTIc*'gaD*ʥla-\}$,eBj pDs1<%rPQ BEć' +! +V; +@ŲJ1I21 Nx3dk +P_d5T hD1%oX>驕m'9 El6۾׺૜SYcbBl(DVΔWع\|٤̫GŰHe၎}hU[4QЊ$U5M;x,*D;iSU钴$kERQ󭿮e˝pR")s,:ձˆ/+Aߣ /_|j{aۼfힽ ]]BF޲NG=yj mbۆ ԹESi|)0_ƾSq;p|ϡWjKI?BGb.Mv9vO.Zz g ]*SպZ#YڊUEŦuy HwŦ[?j):sr?VsAf'zQhIVuj^y̾7.鍨l|k16lugHɃ8솉&yY2>N]CE+vr=}GWiypFKkj,E|?{5ׯF_]`OU8wN cfi_9;t{mİ Szfj #tZIYjFNZ|ƚʶʎ!6(] :ʦc}gB; 颱Ȗ0߆j=RnZ \BvGޛi랙zwS|Yj&x+XEjO6#V{EkY1fSga.] +#= 5+r'JfuS2s!)-?UuVG$ʕE+֯^1SR,~uM4_/ /@-|fdVtKN[N!jtY]U + ԛWon٥nZjMEtϺ/lZ=ҽ;}gWzs{i|LJ6[ɥ'N_?h'3g*/%[#^'9[Ql^ TnSP>cz꾶 +TJ3j!EeDLYp,{La"x2)gi +Dc{:QLcrӘTCYꃬ+16}5)Dk!p ;fc)".$3w( t/],@/rۏ"mp< + + G&[ [\r2PQGf"GR/{F3# Xğj#2? +@$|d2nBt6cU`7tuzJո3W Yb]c!؜Y]oUÊjm1u%W)/^rvd+ Dwn]nz1iƒSz2eK2%2J_3q#X .UynN/E̟R+(av`_>E)G{8=;'u=u%!j*qaS5T™d1Ȧ.Ӵ<5kdVζ˕ۣ-&򳒞 +ܪbZ3Q#Z +q/#1"L-Bο#^J +!PQ3,`R&itn#`8@~VG=kk +xaƮڽqn34[rf*@ +Ѽ՛SlViw#*+F Gb֪ 'z+Rb-7G~5DM`p@y4p gZp<9{29l['2UTZdmZhU@k5&j T(UZma\  +RGX.-0CGC}_θCb?$ `,GD @I~ +.}+@U1a%(3, LrǸQ{v\g+?-ΊbY`0/`IEH}"\,xcS``lj/ˤ1 {dsԄ88̭W|_xl;A7ptXVE^fW"BnW 5֖}{ +-UҧYJVu-аR o_u7"hbfsmUiwPmi$as4|.Ly,ޤ#on1qɤ^9kym޸渺1VMd*P,.pH XĀ"c$,2$ +cF|d=AMRU0}fB30&tiH䧠Ā.D^TڲJY䘘iL$$d?P̈tZ&p@"9}F$FRȈ!dY>< .@88L+q% +b"+<;X\vk "Ց*u#F )!ޕlŬV +NY`6;0P +̙p 2!'|$FE0j +xV.\v38v>*)VVNe*ZԴWBJ +/V[OOӝ_f/L'JԳf^ G* +[(含Pn]eLw,?s!ڬuܿjMNy`B2C<_^x*Л͡K|6W ~//]m١dIѿB7ktӵUS+ٮzM`d\]{VK Hu99g# +P.r;6Xs}OÙ&eAD +f}l=;J ڜ9L,&;Z QQش*ȴvCw,N~3=Ojy9 j:\܌ʕWs*iq1Mjpm~1푱t11z;+3.(Td_#a  +gDSߙ%L]MyʗcʅKcfu8]HxCIC_TQIcޗϒ#&)E"%q+Ģy/ץ{:4XZݕd(Tu+>բjkUBbԌ!²T&ݍy&c+Ի帅Q,Xf9q;a^!12=ru-27m֡B}m6jSQ)&ZjŅVBI)BŭZ V(ףmi:_j7u7Kפ7"TXdUct%uĒp+no4ngr6*RE)Mn]zի!lR^$icؼ)\ks3 +\uX{Y3f֢!\ꖴIz]${KiLK +SlQөik̍Zo٭dHEz6`e~=gO_K )bGHT"ɗ'ӽ8I6}'t| +ӳkTr:VJߔ#T%ԙ͚(ʟPּX2I#D)QaC#YMezT PO$De+' + + +wM* }IU+v@'4gbU@;UIQSj#(}5*+(Yp0,*D +8ջȓB\bD|L( +=- +[b\ 8[tH`EYɠzvؗ*wB >Jxھ='+\'VD-UrCw/ADK__+v@S6x&MC>p&W+ +~[@?(5x#5ߪ _z A-:ܺVO땦D{JYܾ6֤N5Ѐ3 F Ŋkueu>W0qJ7{5RHZǏ UErUɶ^{cgq7O!gE5;FT,Ũf72FHjC͙Ybrc2nErǤ<=G:ml4Pu"8YY# e1¶(V_+β4u8Z- |49}]FmEed73m?F5Y冑W[,|&'!Y{1 +WhrI6 +ήڪ0`1U]UQ>i +=?n/2 mOzKJC»+ފu.UzذP$%sJXB +Y0a%%F赣;*i?z]/2u}c#~%iCYVY'{D<'hksɞ}:SNjMc3h2,lUjMA +/ԧFݜrEXpz}{: Am;VkjLFZ/5jۺ5ݨUj OΤɿfcCrok6GVewZ*vg G:o)_8Z|SpO8Ս{GZRMA]OF5yN/Q ZkWԴMh+*m:ʷ%4V +{6cCDCP5 '2/Q|-c; +{d87b.OFh+h4[kd󌥁 \Bssc1jb檊MCޗF`cJ)+ i5RQLciWMh!HQ+W@UZܞiWk(ȆE4*Jj$+,SbS{7z-wAb.wXu&5M;It@͏QUFգdD3rڔغz~OzzW +"Q 2H?K|NJy|jb +SkWjFj/KTb`gi}ic6yrَi3CCrUfo0TZ|JH =cm+b3jjxL;߱H,P"kIwAZ]ajxPVnΖZX^e8|c-3%KpUJr>} b,t!\34Lm'곫HŖ.B_(B~4"dǴG +X +d"2))}dwsv֡۽;}ҭs/Orn) +9cr(8:7l8Kw,Vy}EiA7y29:"9,1V^أeGVZǸ5OPJ1́qE|USQAn﷊P.,ĉ +'S ܀ng@#n6E8Gء#'m﷩֕,X-r˪u`<؁FɁTI p=xrކ%6 +32L>H&NLJKȩ\rS׻u9Y +#yoG⛨gJ#/LXx +aHu6}Y +QMw +l% D*N2F"qb1O\^͒\ 6 @ +XF玀$b>yrߒ&qzn^;)8s:Jϲ;ufOS 1anY5{6P,NkOWX +@kf?J%=,AEHAK FR==]2G`"E02ScȈblp3`EJ`fb +'cZ׵xaH/ Q"H&,2^4q=G>F`v4 v{lE##%333~=pe:/ۗɶ1N (ȃIBʻ +x +ƦV$LME$-SIԟ?1V] k" h#"!m?̌uD s㎤#-jbcnSY#ZPA:Qb"E;canCF5* +R76TN^'r]0Dƀ@<{֖L $U2;B@"[i]sFnNV1(\UM +{0^AD M3 Qwžn+2[./ ԫ +VnfN4n{.6]3h:m:!eE2ʠ1$7֘=Ѻk\KK1FlTmMPz2>ln^m؊9:I9 }mmAwB;U(dϊQ]9rz#?zUk +x4:uKe&vWfrX y4YnyjXg4wUoBvʶC;sR܄1rԮ +u8ѢO +U #;6Ԥ:UasIA, YAAq"SR(! +-G0U!  rL|1 "sE&gBm9+~1aD83՗UNΥ.[r<|t0p(&LaDʘ0 eBN*SĉD9. dc(9IauP…3dx9. +NޝG6v)T*Af_eeથPx4טga*-Y(صI +K_][[ـb,u) +@`ۏ}u<1X0/ ɮ!=38c;t)dlVJ* LUZj` +E׳ z=T_}`N董I5ɺlG'і7! JHhW(HVQO"~_5Kڌzldӻ}E|W`sDՖcu V٪,ӖtSӑ?6oFbEA]s~"k~G"nXD +w%z!Kz-izuC 3Oq2p9eE`$s:0W_yXVZbBI3R%c+)P +tci (ۺP8mU,|{9R} պ+,Q7!0Z+CZO"UoTԵ,M&6}~xv*Ƕ +.U]@&&LNX0+r- P/^N*6Gm2smu6ӘɍfTN@|d1Y,|'|}m=r:{PUkz|u +YC0 v$yz=J?y6W@m܍EZSJٜܖC++f]%n/f-R[~4, 5r A=ojGzM=)tXeOm`?nDe]j*,nK'O@p[ +Zܩ"ZńbI^U@VzEx+=&Mµś]',ʁ+͑N܇=ݼ'm`wONiպ&5s vփ~XmEX|A|#~7#z+FVz{Z̕=jw%Cۜ],7-i]ʆjhu +U2I8vRS%̆BuNG(\\hgrenEjWzfjXn%^ՔM%?vdX7ڶ ݌,άbʄ{p0<Ǽ sE͘=!NQ2´U΂ޫ J: +Z߱IT0)fS1v). )@>8"?h4c./EuY>Ӵ*(UҜUh9 +  +f;+pJc[^E;mEGscw骒ܩV%5ă}Xھm2(kX$eEf)Z\&f&dB}eM +e[ec7SNlvm[&yZeN,I]ݜp ๢~#8_RA.A4o`ibh ]jU|^oIk3vV3Қ7OTap8lӘPxc1V5 c)*U+"5T%`?FWۿfF}ozu.'oG]c^_zGq~::Nf5ٙ9$MެX[ͱ6r|e{ukWg?kWnٔYzR[Uue~WR355ū+RU7 \.:P;C*_\-$L\EZkClC:Kb/pt7*[Af3B!S6[6 a&2MC{Ur4q,M;"~E(`䧙bB" ź#yVVu6lKfj5s\[*kҨ}wElь$v{1HHno#;#Z-MU YYZq6j*VTLn%c113#Iv;k,k{[47j|a T#"ؓ55fRڦm"C(uRykx%Xh]l A&})Aiz4KD0mMFXP ^{#]i&CPrmڪ mRZsy=%:phU+|>&mbi7 3+԰z';CQm>2:cHep쫑jMOU + O&o jm܋cC ȏ3Er=FXmS? \ a`Al',Xɉ: + +X^JL2bqҡ{ugH}F]UmّKbUYO7{Zs;2+4Ch?qՌMRn|$>̿;c*`yi2u.>*5'o͹rXܛ a)žnآ.n +1]dm[[))VaV'o&p؍?,e 6*5eURZeET)Pkݬc$p#ڍL߻mOTEOg ܆9G?h"k܍:K1̦vZny +퍤IITuKnSkI#]N_짠؋1EroSnv}0"df`YW.0_Ca̯_muydbw;}hdRN*K2 ]N&!b7ZS]RW'PL.7; +?jf %~iCdܠ8\"˲9PQF|BZ*%k# 겆o| + Wg?ux=knzҊ-uV>8oVC&uAǖF!pf͏Ft Fӵ4Kd2љ3y*ʝL^mn Y)!uRj@VBHT +s +J'JFxjط +Xxrx>Ov6U.1)/r!%C]gċD$%4Q\[J +Fc="`f&$gq s? HG-eXL+>S= +ADL<"!(30eJtB:ԦֹbR0TG0]S\OuD؞Db; +%ԲŦD*a̳~?8~a*T3ĽV9;f ʮ\cŷn$wzvoNޝ{v(Ԯ$䘷掟Tl_i}_f$xvÞ߷}BoS赸HYNbmcbowEjw(5'\ݘ"ܺVA +xbY6ǣO,X+:)t'^,kuYEvaKLC^Qf.œ|}yتR\NV6rNǻ3dZ'L5hA3ֲUjcߞuj.ׄAkZv;r +όE層Iv\NLu)!I +!}l1w lLdČr=@dr,[XխjD5$Z1!TK*y+(Q@!әň,܍}EI BAf&Jď ص-JTJd Lb2 2򬈈Ə6d3+,.gȾ PY 9H:զĦ&1d QF c20g3#j1Keh2p`wX*Apžr|f1 A3kc7QT*6 +N1kEX))B`lJÒ "b)qmܛtk[_e꾡.YBb)%&=^!9߿15 ׉zLQ 3>4A  )AKڱS U/!>|AUn")=W>ͻ1OfD P3)[d/ 11.L؉is J:;0N^.=6=Z9M<1EBiȉvv*[2qT$-n]I`a}SY0D]gV0X aj& +*'o6 (HgEP Hܫ.ݵyHm$(v2vFLanmJp)>T2q #q9^Av@-wQQl/WS?Z)l}Z5ARA3;[f]4-o3Q3S~LwRm j9,v121̓vJf6\[%6M`k>~m=nV0Π #ԷQnWZع1M=Ean#qFcnͷ#qwC%k_Sh^bѵV䨒bE/2jJhWRС[-k6nNc[>Wh90ȆD9,I֘g"}1YDwy"Ys4a<ۻ# +jr2X mY"4-됸 G߽/i{Wx~=a=1c94lٌĦ>hke^֮DC\+cdt^vzֺNֻ+8vxVDEԊk4Ω{7&fsB=+U*2;q='LjYל-=.A+LKI/PdJ8s&HZMͽGoϢ۲yn{lfj +̞&,V汹NbU V6vf¯f{WĎ1ԞR3ԯ5zd%$9VU +QjG3$)tcu)оM.|YTஇXd41(CF|~A& +1z}:)~RS& +|z74t~ymug{Mj|NibpU9X^|_h뗪- +|_% + +j,$VXLoGӖ;>||ϓ||7 +xPOzǬ=Gz=c{AK!Fx 9NҲ6fYtrޘm֧ZwZrC&U|"1J4†FK9hݻB?BkR<P,dO#lR95^&ZC;b,Ρ2ůUkc +yweK1YXhQ[;$ջXxo%pUcθ+[ڪԪbs&&sXsY;5hؙ(2Zr״V VUt<)='*įp +J7h6m +NC(C/܂donAenM&rh)_!'S3"yɈ +F"V6:z*weTd >fDP*m +@w +䠐HFTF;QhU\ #\1sɒ#,`??M"V5j>2y7nͣ^m+Ti׳h%Y'C9YH㚧q:_'a/VmlF&@*S +ڗu ,.DZd>{wǺ2(-+H +7TL$Ͼ1bݙTEhxQ"Y/;d~Or'^z2LvK1".ù +YJڞxc_lXIf qVۧ>kXoǽ,;'jrhG^M9ĵxZ;u}R]5Uf"ZfuW#6XTU dZH^15= dsA2b ~1VY1f ~7S=f9}p,v5j9)/Ⱦf";2z +3#17+)}K!J~4KGnڒR{2CՓ}Ea,'lXOꝈݯyJT %swW_rd1fؐ[2Dbڊlg3Nl\ -||ώXP:Gpywխ4u. %S818LU +t=75":s<-G5PP \'TBP`]b' +ޓ,9"! Pゎ $)uuvUj%mX2f] + +"bF$gfPB o~ر] JTD +D)5:  C\JJOT(fqϏ;w?mzm)L>Yx1nfR +KFm6cPkg"kZ5_:4 C3ajrxIOQ8= v=g\X͙]-Q3K%Gd(,Q,2 N c\}Cbݝh쭌[P\z_+󝜎B킵~orܛZb.+F&lЙf8;vjn ѷw+_mtPcqWܱnZjjтv׷}mr63H )Y\r1r%=DFc+r?ujLn&//4ͼ(zTQRnB +X]BGJT^^nKӱovlMc'-ZǕKSIʆ-+Zbխ9RѷO +u,t*vfa/,YʷrTٷa2U{:t2ʊdwÆ=E \DZz3Xbf51b~w5jX{p~LT"2RkF^ I$ؖYZeCaN#/^R]3?L׵+J:O# 'Wд쬨G[J%0-Lbt=4ҹ3Oiѹ:0=AMrd/qܾDߋQVrἥyEɭr$!A%-qL(;Lu=ovN{Pѳ:?1vnhks79*YFC*($}Q>M,5X C~Jۭ"`x=`DDXY 41XmؤF+8n[Mpo~P}yWkWCE]Ƶ +j,N7;[bӐ^R[ഄYWҦ +ؚZ@20N-S);zz3ۖQnף 6*rf+ #m a][cg27 yLu_UGػu5&#ڥf0p|v+m5K [c1ƕ/_N41O'nd+㱵\9ׯWWOvsg\,bJatlp92>Ic|{xy:+U_[7ҦtdY\Z,RyeZpQWX_T>A%6Yub%I +Mܦs^V%3FM4S]}C}Z_ftX/a/PReJyZ^6kRfp>uAWk. Wp(KyTH ",֔^Ѩ rԀ6%|VRT,9%`Y @X&o=3ZͲ +vV _l:rFw@gQ,P ~g 4-dQͪCZPoņ=yy%le; +9óM֗4q4j8WI.J̽wl)+oPG"29ӫ+ML{qqi{ +RJ]zիiBV +PF1#Li.#g6rU55Eի/b5e&j>3N@V.jlq-N[ r\/qAh[kCZnGeyl 'œi7%Aٷ3#[OޥM׳9k/V̾_1jG'l BNŻ܅$VlY}ntb5l/tԺ_LuK^u&g)uܟr`dddZ9 nl5m*K#; 1E8Pc˚(1I:X2.Յ,IPf}ob`f(5X.l9MƬJ *4q>{ t +dgLX$|2)@T а&JUL9Ÿ G^0PQA +L81B +\ YHca2y\LE32"2ҩ +*Sih.x4Q2I:7wTeQkT<; ߸?\ƤZjSpJvm,a*[fdiMjRt 0^8`)['U]7(2P0mk +U +޽ îUfFEz]qS]WtIy]W7.F?G +Z)id#5, +a4nqxFjcW)KY\wOp3T+4.//n@ii9-CQ%a9;1xZ'd^RTc.^BIHTȚWӸBؿ9M e>%&Δƪ +bLe;Jx6'Dq7 +LycK==jF16N{JZb;DXүPof-M1hM +{оuoG0ゅmE|6(Z7WS.eiiXl +4L&<@&o%ϧ[[M{YنѸpVq +W!2:[Nkgrw_,~\14od1qs~i;?S:s`3{EfV{AcYsY͝MM%n ~Ibsp` +1jjw}}V[mGq'VM`qY(? *Dċ#$bѰ)_f+ݧa ZREf~mB17XJgQ#`+60YH]o +v,s4M˯Uyo?]RӴvHRMWeSXʑVb.Mՙc}RKLs Ъ#>`d\2%H瓈 +y):Dq2Eq" zq +1eY(.]'ö]/mYuFijxd2Y|c}<=g5iM%@Q;zl4b1t34aY  j/+eI|Z)"Փ8{hJУHfZp&En2Y@ #5h3j2'W'j3lE}[EQ1ڲ1qT1 d-TӃi4X,B?  +$I%^덦 v]ou {=b$Qzlu7+Aq=1F.ͺm֞=Gr4t鎚kjva,hM8,'+N;ae~?,ʊ..evQp͸T!hOt{Lmlc!5F$S$G0l_ȧ`s]-Cᘻ9 Uar% *UX,dZbN9&? ,įS̥mWhdkuqL]+7ҙ\;Srh,N[?zO[-L԰Q*yG"]v -aIeDfcMi$5Ѓ6O""Nyv>-ck.ͯ,yw?G㎛ZRUb}X +۪_N./Kbir[e 1+ JV0gHK= AC{HcYc~5Cvrv+6fHDr8'0v ۫WRy@7 +AK!zL|&{)Ժm_x{Մ~ӑˣ̴iBԻX3`= fAO5myjպk=ْ2L[gwr+^KeU`Ȯez6i.Ĥ!+8:}1R`cq^3)4IR+!HQ +Gu72tj`V^lUk6*HSu7 )l Q^\c=^{T{P +{y,qtᲳ%\qs#-EnS1|K+j[x;f=k:8zRc6Mk˶Gf<8X6i̻ܽERBԞo'RӘJPSؖdu3<qx]M5vk%maiYjbt~:5MCZ eucS^qζf\0+K2+ +Ȟm\,V;*䰈6F +C-5_`mB-3J4y&P叕V,|/5 @ ~aܝ–;l8Pvʖ=uJ)M$Byu?%TjA!&DSq3@hRBѐ  Ձ@0=HVə ~^RFs\`vx{Sg' 1Q$#2!$I0V]$VG=}Nޯeag7pa5@P`5j(&Uؔ;wT +Jc\9ۙ\Ȉ!nM ZUdd)%΢ +iE W=mm]7,XWI|mrnGt{&}Yk X./OY|n}5eqzn٨ +rS+%}vn:3 S5HTrӲ؁^a]u= +,qBTEq$kkabu6^:U}uz WRMj4jzWif' xj+NKȣ̴꤫:pXGzg/O萼$tlw@Ӭ.㚞-ܝ#jUXTa׬S#Ǯ]Fi6Iu +p$#y@=V17JnP]ZdV#dO Jzckdʰ۸d=tgNUrtz8 %qQVmHVU8j%qR ` +[Vg^Y&-N1Kj,O,HZ@RRq$8"rXB̋>K[ $s-& +bXݴVciyρyEH2Sn1N*TopX">uľ]'Y86!~x9 +ɗaQ/B?=l(i5L8kX [>LMy((.A0G<# |)ڋ#c +\]`1.O +dH=6+8A9ER%26E`S{K*R6r.vY1bHR[6FY:聨0!t +}J}BAy8S sHawaiCH$t+:.~Β 9 +KCcyLyTUx T3xA +b/a70YYOILLy`(YhRҲ@emC +@.@_O´1/l#uMTt4GlSBLM}a̪u!:NU5rr1l)X;Φ9\`ZDSpzCV*ش +fbIζUEtKZЛmJEd]$wrUUA.ǃW#6@Pul{3 +jW3`dbvy0>CK(UFx~]h +}xW;sg#pУn; +ο%>7 ZB2&o{^ּ$ +sZKBZZv,XCP&kuRV%[#,P&SPg[aWD +8_hnb +>O+j~E2gzw&&b{ϲ2*ev`PqE4i"gW3XڇlkayBl_UuZ 84DL~Ϊ2 {Ȩwnޭ"1/Aj~Dad21Vs +ԓ5m/̲<`0ax-85K6Z-#ޟz#m/z؁ IIAWG&)?Fv:Lj0M{= ݂J~-ǚUٚgЃZK[bdM)^DeozӾ?|2jl+0%JMF"X"ƎH^Bգ3eD0 +w)Xup(5ڑ ߽<¦ |M4U]tqɍ B_b)_R67†) KA8>,07Zݗ*fڸqZ5d-j +cu&`,A-30+A@*$sCcgKngğ +3vY\MMWZuVϲV1X62kƥz^n2.+ON.EM7wKd-91aw( E + +tV=Y tK}CS@uү6pY1wLg,E,׌E~BЧo?{~2kNgAzs/iڷmi~_kJ^UcinfaBL3X}Le:c_Pa}CKq)-LUOKƫ&g5=lkQmKHǫOu M#,پr&uK*cPsXcd2n4ͽ5R?8M4jRjx#c>2S֜KnuTZfss$viѹ~D-4W VJj;Pkc&n@͡)c8=+<atQϩzk0.Bd ͞[2~V4DѬT˹ؚNyxҾ̽vⲘ,VN/WbC%mmqfʐuṆ2UlQTJI\^ +W}^լ|?ng|Ǹm54UMgpN T}j3}9MlB`Xx4TCO +Բڣb5RZǺXc'!YOt} +xU˵PhBo|ݢeEUm~FԳlڊVg8c.5J" +R2s"q") TQQM] +_ӾOBnNNdmu[3yB֚))vW%n +ւJY.7V\+K졶a4IΎ,hDk9ez51k(Rz5C\ ~|k +)ڒYʷ!m۲˯Ƣѧ艤H=o^#UoO#-M_֛n_-QRrN}~V!] e#^~|ꌮmytf +y!XLpdeʉh-?5Z=׮sUEִ>2ބt~_@#&Wj}b*֧sSS=ffֻnf='ϧclbth\f7Q4I4]^_50WmY,lgWԮ]e-\b)Cn'{n)h:G@j aXnd.Îwalev2TIݮ/2 "5I uҲ&̱gֲ! +Cl7rY)WxJVL0&med 7w|z=wz=z=z==eSTr8xJME`cV&FegF O dv|v'Uc,\nbc[:x(ͻ=^8U}t7T|Gq=*8-3ǽJAl,2Ǭ|budu 2eBJヂ v%S2Wg|al\"#JؑMgelO%üV_1;{Jj\`Uzj^9L;mU<"+6 +'4;"VF.]Ͳf65y1uV^ZEJ"[r +,OE ?JP6 +^&RE@ +UK_Ox"THvZ)yry7ε"aA4i#Y^jRF1,LL_8qA0&VKFaN xUޮazlE֬vM^[^ +Ŕc]*w>`Z(j)3%vDTp:[[A\KSo6Ňm U-/Ypdt..2j16;)?W+`|[bҜՌ6v4UmSȴ]؄kPdiluMW"eԛܕ@IFd"'19s/XpBęemiˀd| lܛeyM<0FgxQkh)QѷBB.k`$ևǵZBdfJk-4xiI.H3-5;.2cYIYRBzjk 5M0 +wmP5>xBq"KBa]vMEf%Z +A Bkp".d=zZIhS<ñIJ1<@1Ӎݝ^e&YGeRp̨9ɀ$u#vs*pec ڮ+Tt2ZY@S_rڍK[ YYSzTc+&pZg&*I,JG\q<)JQF< = *RvlU(m'^ԟ#զ+*fRԫKk WPƕ!% +7maa rh'*3lS6mք/ؒ eǡ1 +3,1'=Mbp'zgg[Tjuxm\CFZ̺' M~Fzn\k ;!zs Q^)i]GMq2Q5jX\qCy94rJٍ'.; $.{g6n6/{t_onhVƵ8mۭM2r3KjU3^c/]0ydM,~O=UuΫMU5oU\ӥHr:PneOTo[(ӫIO jJOhG8 +"OSDv_>>7f֚YncNg5֘<܇Zƕҝ5uch؝t\bt7[>?LFv&VNv#'+Z0ӋD㛬F&m'[B97~|X3*H?m6*Fw*B +N;i3i +W +rļrXW.G{5U-H0FQORl=gC!mꗭ`ֹbhe +DK^]!iͫeb^QWXZGݞ&}=E`v2I4U% ʤ[zE_4-ʮkF꠷[rv +vI$33oz=i +ˈmL«0]C%^ՏGl6bsצL,r9MWBXtfҷj* +Y^gi[>OIWZFXfFjNYZUCkϻO~n>wQՊ84f(NW7 B,+}kbq)N/|&7)xmblUFSIѽ0(Ûίl9WXhWN 72d|P?.S"NCcxC !>&oǧu㎴]#EV0)=e-\ ++ wtܦkxZT/2 V8$f,_99b½N JKW`fط=,s]uRV c)d\-fGCfJY^TXٕF{OiZk]%ԥt9C%zڱӖ_r,^UcVS~Kkl +Ula 3xe̶S {iљ5-{_ӫZW1Bc#5n'D4y@-i-IɧAXW@+ae cfC=ƄcgxJO^)8vK"1 +NMA†N̶SVYfs8 '/T3Pf7,DP|o,6^=oڴ>ѺjbtR [ec= +?VWB6djcJ:t7 +;&i7?ꅊ"CmtXW{-Z\W]P~V +|!_eVe?%O"_qM+ގ譑]jڇEmfsLj=í{!59q7)7G+ ySSՓρ_Ԛ#Lu}uqwy\7+s[cVʰ:<{.jKulQ̷ꖪap=0#>c*{M}+!JYsν0Ο)L)R(Y97KnUSхԡU[%IW8܅u#fFفܸ;UJV\*8<@f?LDO?RL΍ξۭVC +'S?`-h *Ksj \փlY-QyVDqAY#%RJz ++s9$ذnEcZA/yUyQ$NfԲea.EZeB*dM;SF]1mHWe;mh +O +FfTd)(@л2W%KX982 +U #jKn#aiz7J*qV嘱}||  _%r%IxM.Mә&ӱNŻr| ekKRP#Ta?mVYnigq3F5 Fzn`u(NC1`llb2%vn~݌&Wop޳6C~u*2xn'YY썌Rvlnb͔M|+B7%O>{쿻Q:o=@u=?Ҕձ֖c,. 5ӳDs;OȖ2.Oei-+V;Sf +AR]襺/`VtU6]"ygJV3n>5\{꺓FP#]8vMvgԩ.ϰfq.@%5 <,}=$+Jm7qm~HЬǪbITY^ErT3I8c\~jV-c[Uo>CP$6>bm +`ޞJ"۱Hu"ZJ(#kkAj3Y J(j o eu6IxUpXM|5.-QW_xҭ[b Fvl&7s*QTpX2U Ycѐf{/jVi}!5uyab iĻSXL՞.ʆ\kl'SzFVw+=c_ʚv+fd9nGκ׍|a("*(5^l-9k?j*zJU`ē2bnݳ]33FnBZZ*] -5ӰʮoL6T +r"SP K s +fU'$lI?D:n$Y>( +;+z}a'2## ́6!r"r@,͝)2 %-&[1>Yb +ͻlO vn}nlND`7 2W;(|SOtTy-+xzU QT7.d<"\ +d- +cDD#IG<@a) +& +ńc1* +~#Cd8ff^&"Ddj䋝=n,G6@D?<9?ܙ.D fb;GρS +t\3/DOiEk=twȻd&>M$^9+ׁ[ +ԨE4"4W.5 TXTx̊V {ڛ>-{zڪC7ɢJkbiOhƅ-(\mN$Nl/Vud$mys2R1rؘ!&DY?3xfSȂO1OىS3Kf+m|Q##ϣOV.KΒ뾑S&stN]Ckz~KC=,t.Tk%.݌V 9_]=%Ԫ<#C/Vd\=ݽa+[Wnj4;7jg!VPJߐQ73YSXe܆ApvO{z +U`,boj_s{)5v7i(.'Bq?RiVmX &*/39mC}wr\Zھuk=#wY^vFO=j}^tgLMLm&:1>:.4W/=ONaY NM( TUT*rP( +("9Zgg +`b| mR +% +fG  5|̚C>6:KnE= Filjim7KmL.tTQw/draqmGuItLj[d`Z6ΑhZ#R YP[IT ++3(iҠ; +}>KFmΈl6{rR4S ٜأo!f'Zmlzys=35o]Ot6ԝ=XY^NuN/o_O33:st\;;}?f##+v88MJZ +Vs!65*ǔ!Jr?_]Z7^+YOgvBkf/o2g[+cBi}8bu`tyz ]6 +]\n[ +ax +بܱ[67xd>([t?Dwڇq-kzm~gT/Ԛk1`0Ui/+c%IXu/ߝCT[^c6CWj< vCGVNN_׳zUӴ{WqV Ug/Xk ݟ_mg/'ڟ#_-?5-CW`,]5/&|v&L +SL4nӵlvDmR3tf0nCK935Qtͥu +w~j?v6hezLv]uze3a/=gLrXzRi:%)-VS6j(U'e> MG{ v.P +ȊMǙR忖U +[K?J{M}wͦw4'nWڋfii E[R:hiy`N`m_dq5鐻~0ZfY{ ^=d[E /5j\6]!.Na?Lܬ r2xxiZr+ˌoyҜ(ZZov<`++JilڨY=;u7"۴>t^>i~iw9>^/Q +x"]iKLJګHct<^ħ3v+XҔJ'Mu,y[7[l8(T׶5m&װ6*e(⯮'1>Eq6Q?5C + +X7O.?&W4=_ѝnou6ˡGQŎ|4di Vmlf:w('n +ЬqʵPJƐ-mdYfG%Rbuzϓ-Ә,XUjn^ڪ6M xi&4!aeiO2P2¼p,bDJ`D{ +c5`Y]\GC!:dʖA +@UctV ]cco +Ūb֥2l6D!]%' +q&'ӭo^FjY8j)i5 H +")Y[|MX,t4H0udե7"ܚXB +|g"ǭK$+3 :Wkr7|a5ur<~HTb"!BȓMj$l_ ={Fl~S,e)RQcӫVf' >-vT>[ +41MǦ_jz3&O{#g8-F# ,|~U|#&4LgL\gҹ;Hb]4*@bLMڶQQʔ2P oTKL11q#9c͖a021!kHh<jQIVd˟XA +6?¬|y8M@sT23ߖيIvGg<-uޡVî>6*b7`Sf7FIT9m:r^14~O)еhbqt滒ۯBDڹi]}GXآ9b=y|OoǴf[)nvoIe7'j:*i{+[Eal%잡դNFNi>JOwÕJ$Cj +Q5KA&U5%Z\_{ͧ4IiӘtbZY5l-$cqxKMӣY-c 2.Jo>ejQl6e,J l@1;OG=cz=Cz=Cz=Cz=C?'A}^u'7VڳK]wR))9exhǜX&ed+\uF;mXˡ@~9xѣFCEˆ6f[SUu/JTMrrC~BwUeˀ3qK +/&*WOʣH3*z.}mm+"grm4S1`n]~Xբ# Ll^A;WT"H;2֪2}L!rvy(L"̵Qjrɡ`萩/D)KZ +Uj5>S)ԯ_C.|Z)ƨ&X]s$@ԫFGۚs$L%6*[f?*4eF BԊ- kOcPo_ïc 5EXeָɜ5X6nJ ffiV1fkvlǡ K8UdUj/L}E |@vzd|Cqy,H1euiuT2:#V"pa5^5j,!9G@5dKbfL +Dbbf'~sխ3AVKy;8ScMfnTXWC fQڧ~]ƪqڗb.miYv1_c#S6d,RTgq DLBꕆ E}8źG5m+-o@1މl=KӨi^n6YxTF˒6aa|1r>4t ei"4#qL տp5}-y;[tvqf֘)+ĦM1,t-EMYAsORe%_;]|]\hyٙ׺gMnHiE(Ş"4붢pL;t +pZ A!pP@c 0(i:m]Jimf{ f*fxZ'D: +$NB; g} +O% +ΦܹVn~s;kܮƑc]oJ 4 +4GkL YWaDzn[DĦmVqqec 2\:1QM+PJU6ͭ1O\ JZ VݫYk>jL:sHyFT5]v+ZzZӻǜ^P|4>\GόCdg-EU{cѴظ{;ϓUeGKav2k(;\Mw2Mع[}%N%5(gY)'{ʯ. +%R'H< +!l[ +6Au &C ͑G$7=3n AHܝ+aULD.A K"sDq +gۘ #3HGĿ#B +x ++ lKo!dk2\2g0duȝѴ t ]& d`H~XxG !$k]&8 Y-hU* `'Ew!f@T1rT3WPIMq.⽶)|ˋhc &9C +ڶ*Xq]q! +l`H51m򛖻+x5Wec +&T Cȝ+uX BϷ~LI|q0q3+D0شDJ"kܺA&αoo!ijU#ĈM&;?a!a +Y.?ԘEw_YtdtlLHO"awAYG{#d +ʧm}} R3un-Ď]w}Ϯ2l|j.,5qy#JKGLEbX`\OBaLJ'K +Y6,+X ,bCRk2J.`Z}4dfdpYfgV@*8)!hX9u5UrhԵ!*WTٙȂޅλϒevqYe%%?nmyLT,*jŽ_0.7Bn8^9Drol2J$N"4XdȰZ +RT:KG*)+1O.MgUR')!^)[l͋v lY,aA#0$Ԯ_."9d}L 4HY\0.Q]%*%<$Ld"` '&"R#<=%zjNAC0MrPv +l)U*&]XYW&ҏ25}ove/ҖZV`3, 0 )iʚ" sgYԁr ıw+V +Úkt.lyfAS43(䨉f*ۓ)P6%[M2uJ"rrrkW֥StY`UkXhUŋc|թ*)/amz96 +G+1vm`יCH\U0 8(!\}~jw*H+2yoC:`_k%*`sWߕv0}V㙄I(BHBdpd+:(4CQ唲}}؉Jb,i}G73/++"SLg NB.)u4Ay~NFЪLTSE`KevW9gb*DŦܠD"zAނ "-Y9  7n>FGI M\YY/D%4m"` t +(R(U'䫝/-rzئ@,"&XCarܓ`2 M~5~S~2-Xc a,Ye+>EJf!C2V/M#Oq_VU?<=?\]p5r<M$[eb̲l! ql֟$RXGJ߆13_vDqe^{cA[FĿ$xͣ0Q Z ;Sؽ +98"BN#f QEdie@Vq>ޏG֢~o`k6nwQKd{&E Asi>X3 +=Ԭ#J .W3^'Kvs_eXjʯӯejel5Jq*MPUB$vh)rq'1òN{vZg' ǮYIUF*WzfmnlH{j`fAe| N@c R6F0k_h.0f:ަM0,b'~4F3;VAƍyz=9=z=z=z=z=z= +ԥ%&͈!XĒWvdN*E.(#Y8(۟?em,~^6fc g]iE{I`z{Sc 粨 꺹UhX[UWBJ`^=ll{)nxqZ9<' X-O;+ ˗.ֵrFݻ0 CM%ХbwwD˒ ehXUVqS|C0[Krtn׻Q7^ +)KA'ƽټ΀,GA*QQ^L]i'̜)Ӈ$ab r;d{ SJ3;i\zSUa,98IHe,Ɩ +w܌ej#_{g +Lhpk#PjWJ1G^C7Nmy[i_X{-ɞþO2X[GލeONUG*Z9v|Z8*VT{w8 +2HU냗 Dm&)L Ѹ=vu¯[Vҩb 2$P0+"/!pSV0"L]eáz"(Ӯu}/APŲ2MP3}dZNYa2W2(Pv@] ߱E +kKJV.0`%`)Za| + +bX\ tVyfaFhnnw3 _ +xLȐw>b!0"\ +` # +TUJt ;;}ۂq$*T 4*4F`,7HUÊ("6{ۭRm=PƧVEdGbTǏn&L2?$ND1;< ]Y kfD5ۿNƾ$Y0u2uUCsY,.{K觩L+Ѧ4Kuju[ zQ?ZqTn6. TŰ `&Pw3n*cWkڎuw0oeٯS'f@ٖ٨R[Xٖ%qP1\ag`F)D/M7l}++?0c2SdRbn0.>,]ZJ `Ƃ%^t`\T<\wOĄ +Dȯ`-IbYde敌uPt&TmtP ULO +%Kra=> gäI$u}P27plO8m0oU^Ua]+u7:qI])ǝԺR,ԣ$J{5sY8Iq0L{LDNRS{Um7]/Vq`ͫb5 K1ɪ-Jo(,We$ƻ7"g#qcx1?!D:IbC-$kBb?ɓ ) +b-V&x`4NOJ`5y@\Ar BBm*2<nt([y;(* + +|)$JP̞!yrEDŜmc$W MPeCPޮo$ +J۩6Md#2W74e0Kv˨ ˏJ#5:/M\ +n[B} f>ǓMYLsc7i諁h'3fJ*G&!y.L:y H&%1 L Ar@KL9,Q~@A &+HB=$GFgdkLI*Yʈ8lf!^> RFfrĶ* Dd,( pHxx,ֈ(XfDѧ6%0dEbyDIyZ0؂DlO vٚ4.n]I2#׽ +cc ^ԫPwdEDYH:d3ӣNz 5ՐvlWb=@^/:A.!"!SE*c<Zv삢]b l00@KĸS"B?MQ%fmsq,^a߼1V q,auV'<4ȝc,n̪AUTl +Ա8Y0V̙ I/Ȋ݆I&fC`rv +1k{2yq>.B){:j: +b]BdLnVUq2aքYDp$ gB e_h,e%% ȌɌL## -29"!/ܼm? ̌u%4%sgq*q-JjQFEݕC,G$+Ej:wp x݁r˳() 'bTtj-v mK s+6`gBhV,YoPYY]I׉tIK3$,Yp +j E6Ƅ +/0+Öi,-J]_Jh3RЀUj hmDH_EYNs|!SI<&Ҵm?*pqEvVr&nqi +u^[ḻ:DjLV2=lLM̯%قpXIA +PC2M2 +?=+ +/ fSƬ .W!Đ7io̮ +`@8%K8Fdܐ(%WAn%I&#`} @֩]NY%9ރelU 1c#fX&/j2ZZ-l0b!bBXH@aU!7- +# g]]wG+NJ%O}“̪$k}qcFrah* jl>Q p\I*-P0I$rDq_DCA$ޒR׻R!ۦawq8$C6߮^ֲk2j QU~s[ S+}1 +h"/_+.;aŶ *`CnTS`&FM\yP9Lڂm7}TKpTJb@l12`MߣN^+1+~Y3hK5\ǖNMxRs/LDPQ5hYs\&Z(Oc!AIv(j́9 Kx`%6[$YJHXYAXvn2~3|M*N'g` +VcNδ+,vy)is^tZ@3 +e<:7#I90Y&0~ 4 "]Gtzd+,BXFrx)D%a1 +5YeoPSRZ& +T5J +-hT5 + +&TD_b +c'=&38op_6v-L,X>o=t[UVڅ u* h Y +$Eo^G֕}a%Q[aثvS[ed$u jAe İvvG#%U͌aߊKmN5qTI]y ⒛ͭL걚~h.y:@2 E[UR/sY*`J2U~-ǐZEj +l'mUhګA$(||JX*SȎ>6V&@FS"5J,I YйJ0 3# +YA*w)$JqB]~ '[ eWR !%A82zpA^: C +5CÄA;qJO6JRLq =I<0U]*Vծ5TnS])֭?%?Q9 $c"c ZS_He`4GvuP`V, duYeѕG9fn +mA6:^T~4)jpцFfV0K@M R}rto +d<%9(x + +1EPUg7aV +REޔ܊ʏQ5*`]یemrcjJZ&#jdnZ +;}lvY ekՅ(o`j12vbʬҤӥV:,٫ ) + @pDw +YJ* VI[oc>z;. +R +Fq `,T7)l+Y18GЄ $hAWepRUK1^,"h&*`R`l[=mVL-`L17I&Wѹ2%|"Pա:"O-˴KHݹI>p5TnK@~ +5dg +X`C80ZTry_ͮnie4lEr,y%.=qZFOֳO|̐+jirvpfum`<J_#4| KҾ&nFy(d(y1QGq%Wsyf_N|/E.>p /)c"Jr`4!եa2qXTO/ +*nu^mg%e@g<'3@+J6df 1MI imVbr)@O6liC+.ZKfF h * D KY%Q9,>r"'!b~#=p]ʞu+1N=uGc41Q$D%~F +eyٯWC['%f6-9^W}H+Y*i8qhB&K*IO"+dVlid\@yC{ME&%nЪ߷PWi{fT +qV+BtHe_x?)0"5Z0+Y=S,ږ ]tե[h0"X֏BPN +m)*HYS tEdHX%!Fi 8ǡ@eDuBD,&NܸX6$2Hg^4 d`8Fɾ?aof̆w2pL NM Y6ePI(lVD [Rq"9?Hbd\ G<~0<l"fˤj,%<`.UBG(#da:213QADpI& VR-0fhDIC71ir < iRb?d d!c0|b&E̓DgM`)TL 4 +`ӕ<ɒi* `Yȡt^ +reJPN<{fun] LO$= +bDǁo)c ³p[5[_ڥy j|vdkt#H#vB +%m=FlB{bͣ`KF!f,U!S +y5 [ +LL `BjMQзZ7Q)8T j%ICnU8Ui0ɲZVNl' )ˆZc +͸%Lq!$'Mť&jy,Rtq{W+$Z#)C $+X1Î&Jˢ +1 R 2λ::CseAGP&QqgfrhdN]Ɇ-8'bhO H_Z6Ԣd?_e8`TRY~ec'dI +7nRv07 */!kD+I{Wf*8*FnL #rrJŠ%AA[lK.b0Tx3.k5`}"pvr?+M`ڛ2 6'*WtvK SJ +VO_NOGL~o{[k5U)0;;0? +ޜ^@ +RÒl PA## ˋqۖǎ +\ӛiV.)jSf嘬Yǜ4 ,c&}ZAҺENf=LÍYlv-GrZ3Y U1izNl {W۾WGd6UVൡ@_Fk }8p\uSǫ7[qew,9Wj[- UDb +%$v6yфۚdFAĂp*< +t׷ֻ[Gr赶6\&N8j0do]iA2壞Jd`+Cdv)ơ6IZIj%֜],³qAn6} +J܉I?(), +fƮ2( ]ͨdn l6m.Hn~N>(_$ {Su1 fXK|gb@gK]ۿr]pZs@[k Rbryi";-UjUcݙEEOUJô{xǹjl.k&hm:car6eiXeV2OkEJ* +-);]mޤ۹=)\-it-]cٱIvlW0/p=/5֎mW +MCEd +2BY E^FaΆGU. +Mf_v +UAUU[w}&K~|#u'sI'{Wն^=mW=RhѸ +QըVoPbZ85CmU]qTcɟ`1G.qiv.^gVg#}Avx>B>O&X3TF\ gC1 +ciϝ1j͝iEn3ļ1_ChҦ֚eڏ%K#]],8KTX' 0Rg=`d#)]_q_^ïuwsM?BbT{)U}֙qt-hJl})*T O_< +C+ɡ+zcƹMn=˵ZdTCٌS4Blفg#] 8ϵ3&!k`WNg;Z4 T\ٔkA&9_e52 gsEg5̊~ȍ"8LDMYIbDOBPM`L59 ltEx@rH;HmB?*wDrC;' +d'mK9^HRF]LxYZ+/ l6ȸx Pjk\ ]tą+V(~W:$}%$GC m`4[ߨ1tQ@R LsXM]+#fZ_sGntQ˘iev\@X]Z%(kڐ&M$>$bcpRG%(T'΢]A ȯRjVr\i +&* +[Ls6I +kPG.ʋPN +64EX] 6eߖ--%^쾻$֚.ȭ%]v]~~эY +40m +lklYSl=widMgtFԈVaXUZ Qx!af͆ŀRiYY +lHz`E + +ZSE+b}-OJe]|6P:-un+\.HW $ ;@1$"I0,ʣVJMXԽW!ci]Ia; m{eʑ;V&0g7XK+2'0eaUUZU-g.PؕYEEIOh?d\ԣ45=5,AIJUNdm>lB7%\lMhSɄ + +2M +}5oB2H' +*8}ʭ%,kayAP٭bP67NE50f]ifQ]Cj6+2km;< +W6G/I,Y:zQ&HKd%͇H% 8l$oW.ҫcrvz\&"y^I\,; oyV5J9ݜLgadVP-(c; D7Is,v Gc@ XTP˕Bݹ>gd&FM9PM  +ns> =(S`*QI +#< AѸ=*^"ܘ)ɂ8Yu;^ҕZcj"xuBY̐>?(MS K+X`L*Kza!(l &$;~`$T2DH / ԝ +Pc +68 {Ea6Q hf Mφ|U',c[!H+`\KYu{fcJ >"Fd"r趘q 1 taiuujO5 c)0.|BKZ,}S ʘ`ǜ$ԑN(FgpwDe%x 3Zc)W埞-Y!#dbb{@vlRB Ep b ٟPs=ߝBqց"1\1\sU8H^X+E_ *0qgttgFn3!\Aٸ*rcծ#2o:ߓ҅y; +@H)o<( FXf'Oj7**x +J3* +@CË4ݒJ+Yh䡗f Ȁx~*TV7&`L+>1K_Cr*-%bL*UsҀ`tuZ-ʼΆxƨRPKSR ?zgiX^Mmަ\MV + cqj e4$l KfBuL_*A{ gqc{ڐǖ94QyC ~MBtYgb Q'xI(<י ^oAB`˴.B 9W]٘^{_,m7A`buVĉj64kC0lC2KnXmzم=ՈBRj~E;BE"0'Cd5^CEkRNL\ԥN lC!BOLZٳBkO$%m +7QU/{M6VY%@q'30#5[GOS7GX,W}b+l,.عaLV=O˵P +2!īKUGTp k:P`u,2xسr˘eU6" [ZkD4\!Qrcf1՛"BjTrAc +V]{iƀ͙ ̸@OwcDŲa*QE5P7a+˪D,E~2?(ZRRFs=Kp]zI(uGV<ӄ6 hLYK> rTNZJH6U+y( KL;64eei6QuOa,L<+C@bT%!# ׃O BӼY--%V!V wf$Y4E0\$>X+EdP&Sܣ@b 22 e`t 9 73=6XeiYISBLwM-%5q4+P@(|5C +51va_س^+@Z +ΞBJyf{QvU3 )e'1k +_ġQ\դT8De-fR vپU^}-=JkM!?^7Y]%\UBFNX-Z4rim;_s3d`ݢǐP–i% ɽvaA4r33v]AUOF\whdoJtpb2i~..2od~X}M8࿨s1,.ڶ0-jB4RFMRdogΊ4Nmj_ J˙G {0OG:"=2 Ue6Rv}C$&Kp>ޏZhg|~0ec~?/`rWޚhm#3dᣔ*UcβhN~U}ohŅed4trU0Ucq) Qb/ZdORQ֗sYLick5p]ٚuiW4~ĥjzR:bfICLɊL7आ6Y4QsG3o|4Ry1Ū?}lelñSSWfva^6Zg^EKGjz9eֻC8 T11U>q4p5>ildr%/*(v20V +i1fEf R+:pe꫎ԫ*,Y8!1N7[6')SP~WX<76 nփ+K-{%g9^ѓfw_MңM%xkIYne.Rdl:(dPx,% +%rKT3r +s.V7 vb,Y8}|E,3_ڽ +cr}]ŹLq88w+5T݄hd֚tWU D.łcM%Q׳GsY|uwcثX_%m\ʌZ"rw8Զ2W"5񊄤j"ѓ#yMK<=Z͜ &VZDiM':o$ƐٳjdnSPNQr6MRmzue0HbGQy 1߂PCL~36oz?C[F_:oQPT f +n;bCKЬӛJVBb)Z}[jKRNCrd+kn (znt SPdKqpVNGǖ$qiu ūh@ČIH0z3Q#323+^UZ㊸9g`vq~y8roT2STE7!W`0C* l>("F.BgexW_]lcrG3}PI`ӴuPb7+ZXg~A:T*M ]˯wXt;V%alq$ю.`r +7a1K ף^ +U+Sj HȺ XW”4-zs!!5.Ta_+a4B cr ݥ¦EaCRM]xu+ozsWش W$6Lb$ =rݐr;'@4ެEL[GK8 &MCN/,fG1tfa<2UT2f +vRL[p+vr5O3R}`]Zkz`CeX\"9s2piOh4Ffw6(d>0g":"&`@,* m[?Dieku͇E %u(SVJJX}c ~'-ёWv& +P5@(A$*̵'ddUi' +tԢ( rrA"S@-2Wk:Fk/\nkV`ڌU4*x`\J,/V5$q)dh-!a#|}ҿ6ɴҵОuXۖ Nԟǐ1G3ϸEn*AO5!f~ef} ]?6n)`ft5N?/e˘ J1‚#6P+j4-OغڱX2Ʀcg.{Zd ̑|o SSr>wZbmU:@oԺW2 +8İfUr|tmv JsT&8E 1j^ +Cq%Gj琲Eɡ.">rWefMf?2M^'Mx1QZ"waq0Frpw#e(z~DJ=c9)=QbsM~/RkyoFmLIv]z^v+.8aQgڣ#SV0,L +(v]΀~Αu=ѬAeFi1pFfOX~&g;vGxc:Gw;?v +0:㇣łWmx 6P9t /#&jmWr`jBP Q]>˷qؽ!kj=4KDd``dWUɊ&Ĥ +QD)5gMûbH: d*F*_#m!ݛƸ@eX̘KE$Do0 +hiL|k#a +W{vvSb-2z^Z0/]k韊/o>OfL &]I%:%X0o%X+,OlkN\{+EnGfz;RV&d 0-~sZH9[ND"Y 07L53!*3U*:϶G2nՁZ0m#"͆a + f $\"gI&0%,oں@Ǔ$`)|bwqfֽ\@vslmխ=j-H&|WIL+Kjf,S]]% +v2ٍܶs6CL +BN<;rʰD&k*Qc(| +TLK>Z @f$G32by ; +1{n +۟QU:r2Xm 0`C+>!1evaD8} +q`m#7+^<&03O-Y(KG⮹񺹜IS*bd&3ZE{Кbn2&VKZ&;~dMKf5p8oihua#.ZGQ hxx!0Z +"]*q+ >jv WF_d>.񷁬ш@dz@XVFa* Wpy,:3.(-wmxhl߮.ncGXڨJk\8OD}e"/-?Kahd& oEuT'ݔz3SšfGR}q#s f)^wUirqMM\4:Q\HehSP a'd +Xo +b漋EA؊fW55lQ'Ѹ)\<6 @{5spuY7PnaD=[r_5Lۢd&Q:m Xǜ_ȄӨjGs"׫ۜ|TQy0Ӵn*5bAGo:Ώ?H%+ҋJ̊׋Ӷ&XQ[ vV$Ċ*/N=9t~6F}'6I»dn\AڋjE@թ̆yFKiy}թ5P(sdV킳& LU ++"ʙǜ Z@r] d.-m-宅Jb-hq[,c)жtS8[5 r[_j+2yƖWzM[ZJ* Z;aƉPp43 SIV!c:<ȭk) $>UFfUIU/*A˼D|cHU kw%ډB0RJϸ!ˉr1Զܜ^? Vm1dvW~Ͳ`r0}l;pcRo]JYZm;- C[`5bkMF=iTt'T?v}R/f`Y,m׎m=@d +0UQqaw+̴ly¶ ++) +LizD "%O .a@" ӷ*MҒghWitH?apȓ2;!lP6[;ۑrL!h 9]kMۛun%wjl/U-JmZ) S;ԥIȔVaMf fmt%IM ;6j.Zxd~[Q,8UЇ6eۋW3c Qi(\@IWp[y]E VVXBQL[!UĤm=w=a&hHah7ܚVD ~j,d0P3]ȱbOZUUZmB`)5y]|eZԟ!A@TAŻ'Ԑg1[ַt<L,v0$9٪%S|m%[YA + +,]65>tӱCt_Qīo쪇hRNM^ڴPhKUj0B.,B 5obc +]xvbMVQ䜛$PV+ޅ +QMBG[E8Ԣ>(g][V6iJcZ -MMW7c۪ hzyŧ@ K%fN1k0+D*V*.fQȢS9%*J9DuwxVbr1 S҈PSJb8]G"!3qKW%Mr-r;hM +5Zl&V&L&ҙ9gKr"/Y:{V3zߡՆk6aatKh/αs߮b @\I/ Y]an VE,kUY{LrK\4~-2+ wdUnWel'L*oz2 +; pUf~}T6a[#2iLfj<11.ˎ`y4pV>RwxW + +]Ύ%y;ޝWTIZJ˄ĘIYf9bтu/5tX2N]gm + +C3TaUb VRF-a'{l>UIEZ)oKZRV6E>Crn]wl5{trG+ZZ])o(ڻ\#chJG6x7xgNL{:wfFXo4*Ɂ6UH+q ǝ? +\zrd({ܙqx +Wz=>˗:r˟ R <q?6 +ӥf@Ph?&;L3oʫa!fILօHM*c!ϸVMǬCLwR^ev: NT ]ѳ3}&{^8 ܱPqbwqeKnDDC6$ZdP| 1SȎ^}}W-ܯ +&&& +qHQN*tYڰ|g +% mx=ե bVFA_C?sr&S&{6}}VM{6Y0jV%+ig=.2e6`$- +8Ǯ G7=ec8 nPف4 0{aX;hhQMټ@'#LE|hi>]>ԺajN&6,26kShx{|Nn +q/4(FU%'˂@УCGц$Wko僟=,ʼnܕ_?_~ϝܩ!H؀y>uhH|YcAM"j+r+:*rE$kL0m%%klO;)2Cٍƌl.i]yq$ O2mz(#mck<@ 4,@ +5&n~DW +X`Im}ԩhnʽan +w]wpTyrҴ8 !F,Yb@Ϳ;{MDAD +d(V1-ux/)è]qM +?'t@a Sw1n +72@ۭ),k])A +a qM{&h}4  aů6NKكL6-۶"3-,hOu8jGX\ +dXQ*ʏ{ߪЄP +vId +lZv(څˉ +}r +` \6, ]#ۢAXZ+#2ͅ+ίv)@s+YQEjm3F-E ya{6WXSZBǻG4UEP]WePUIV`e$1 ~Le;4 ŴZfkâvݫ-ǕW;lc=(V)".1V*{5]F X=?*!3pI[= + : +2 +$1Pօ,VOU0 +aMK<\Vw" 6efIʖ/BıBJ$HTi)zÙ 5%TP=R/H7P;|kfsal ^fKZ`JYOG?p 5rsW݂% I~@WE w)RjWz=j +b"xWUS"KVlhG6Dm;ݫ +}px,1\TVo}#orܦ +r4 + lG + +EvBmh퐷ŐE8I$ܩV +wSrE<+nwF8Q+q! + %1dQQvi|l-ϗ+dȖl_URV9c.Lht%o lL./:%]?UMHMX8jYUю$@ouPlA + +;W[=et5?F$tyJ(m u] +endobj +92 0 obj << +/Type /XObject +/Subtype /Image +/Width 608 +/Height 434 +/BitsPerComponent 8 +/ColorSpace /DeviceRGB +/Length 23009 +/Filter /FlateDecode +>> +stream +x\gǣVm۷oZ먣κGֽ^7nDDA(C{ʆ0df.C +fSӧ2& FlFZ:+Vv9.} +h +ܠ2ZuAI=i1L$JgohXӮ +Ho)?3_C~WqUdN"{\B,_:Dt$'" HsCZst֖8dCZmо#鼷16\G$Ƃ4vH[_!찇ao1&@eE =N=*DiDBzB$hif~m)J+U%hB(!4WtbL!YΘXAYؐCX:B=K6I 1vsۯ!YEei(D|tRYBnn"cV"u"Ǒ̉CݩgNj"H%E^Rĭy$ H HC'Z5ƀo<(-DbC9-|@42ɟf?#Dj +u6FA>t\v^rA!KeZf^eVႴRox!;q7%~7͏QD -f Y5R3d͜wy +ܥ8sOsfgO>̞z= +!~M1"Sb8{qwmӛ"sNgY<"ks26NI]12|V_aR??v#1pMr)%"I-n`Lb~đAc +1K"kgkvm]KtvggIRf:QVK!si?2.'f,Կ֙s9ytm{d^mvYWǤ,>ňBFSBF?n1%# Ec B`&q9α׶)pW\.>˙{3{Q{XCwd ^<`C/s^HQw^Q#]?f]X͓D@j݋34=cݤUcW"K-xpļRzA(džS׾v.TH]L8Ks:Fq/cʵ/mqߕ]ȪoCfh*H=$es+n3,'Kil3)o|Y/jlBz뚍UHu} ydh\k KdˇIi] /=٣'lmP"->%.#wd =p;%6fV[!t!ocn=yWxfgd1mpE16?Wɔ !Gs`G"嫦0Y=7lXM_e`01H)"ɟ[hs?,*>N;j,"ʑl3S~u53;LID(>kK6i ]w=oRGu`$ώXt"2Y~VDJoȡfSJˡ;,:Z.dVVJJ5.BZg#-;E&!x8[(Ur5,Yd.*D4(mV㭽}j9듶_s=ݕ*dT,e?Bj?0/}7t``4 9+Ҩ&_ʧBEٱZm|Bi/HfYw59 +oKLp'v>G9tDf&d\N%b%'^[񖬥W*ci@@lVTJˏ$G>jz\2L:L|Q5_f-+f KnEdu/瑦QAufuesy'93r&d5p'v[ٿmac[3~-Gq!a=g ]Gq =KBL_=ZsO[^) Ǔ?Wl~\F5 +&NuOni.}4ȌF85l BVϤU&*bɲ+!}6R\8l'<I^=C32=.@;XsԎAd; b)M+Ⱥm)!wl7"YRK\DjzhbzWzugn$2%w }O1mjX\䯃Lxq}#m!8E֊˜5WkP,>˙w38{Q{}v{tm^~"y,<t!87q̃+TV\wqyRmό}2L=}o{Ee9j?M~(c +ISӓc46MN+j9 R>\)7ե@ y~NxKlIC:/-9]]G#gCwďX`Iҋr,nT(~vjqQ1OxΓEsR/VT']֜=h]\Ȫ$tlָ$g%g2b͒/Iw^Ȉu2GAk#եfǡw-q5HEWO>_TuR %5孽E[{ڬ<#?ӄ;^+⧨J'd\䥹 +JD4+FM-]]5HkzjMH8rkYο)9ᢳgL:{= {"^o9vr~鱣džyˏP]uB.DW޽/fYD%`d.̹26G?=,*d|"cL Iڏ]T92։vsnK V<ۻf9BYԺT]x4zʆ)k=,5;J: +y H9T|vJ.[R"wDťBka1QyuZ_f]{KC4Y+YfdDj鶦tNk)nnGx}Vdw$lQȬܙ9p&)qƞEt!:]<[&gnGmӲN4ܵ9묩_ČuNT!3 +)Ҋ~ggS'WߜYͼ3cg "6켷n2'ۉ/#;QEnG cs%ɐe>i 9ۈ _K쩠WN_bϩnlX&_^Kv|ƺkfvd֚٫0핳ל?#s2w޲w;9&$oʏ?(x丷4GQ2"?* +DS~`CHZOiI)7;U! Rr +P*컱hжa;*d?Wk_{K것Vg+scjhb!Fh/tMբAK;JEW8l,<ʆZ GT^L8=K)i\2 *M2?=VZ{MS{M6ڴe~6˚/We~<%Y1E:؏M,j*Td o.~#dFC:gӺEM9jwweϢ$lp;~ q +$zˏ]T$H}MnEvbjrނ4G>Ycx3Ed/4([8}y~1Ls4o:tt篦|1iN4ɭDv=c]~,Bi?nfDNk _tLc1] z6~l:YO'3,fq~LO5$Y"KUA +2't [ʉ Rd٦]q=v5Zۥ .$ȱ8WTIHbQ(WSF_*@zz?ެ0VO./ea,R!#KYTK#,,b)GHRd +c| cJHQ~1YvsP?L4 Mũt7Ei| H}!]~2% o2!^78*bE]ONlXP~*RlZC@E<ĂR= AjM;yWq/G_cH(tiR~N, |a,fg0etQA~DAAA޿ *s;=AGۈ?: +I#ߒ[SJe5PU7FJ+K!4(-~eZĉ-~DQhY;)/hѼPc/`xY_9oX[=A)W̩wzS~Tϔxiϯ6tJqs6(A[J~|~!Y-ep?&nU?"~#ok4:aXCU~l9 joSD(Me?i ~WӠ~.ymЕwJ#|נʖWPϑ9X?72~)R HF^Mտzϩ\|?AÏ9wZp[OA4Ə<?k 6ZaK ~D~DAAAGAiY?Zh# G( +K +x%x/(6$1!;kk)Nb&l+[˗^xyyzzz>H},(^UrMؔ~/lGEv2GIrYKvFK9tѡ^VrsyڎKhǾJPb0 pu7J!qR~Φ"6zG*ܲRBy69-Qd 7 c]ӳ6/ +^KG<bY[=qqHi |?k!y2>(PG֊9~]i`+4]Im!QYy\הWc߉nUǥ|YRrζ}eL~Kby6YStEDE8:[ۼ4p36qp31q60pӳ{-KuusUU4fQ݊ wv ˷(]O?fڕ";3=upy(r6]TTk\v?6\ nZˑ3Zm-l_վr}!e78X5)Bnn>+=(YC>m)$AY.K߸Wk^U34kQI~~s؆_1Q5R^̬Xӏ2˭G\QQ$&*153?55)sFܣ:^nq옦;fg+/k=XUV &H pD`C+'ϝ#7% 0䆶G wr9*7USak9#1cgis-꛹ ?kyBۻmXxHOT8MƭvqWZyJvOTy~ANEym=x?-.17(+#2$2-Y]YV^xfB_[Ȭ7L̿NwIcS>J~.X]&!# k?޼qױtfvI؝'>YD$Q=C3? +-'Eǚ~\n]~Y2BpxeMlE>wIʊMZתK&u-/\4y=IJHa.|i'zYQK^/Ӌϫ̜q@^GWQ =mbKY2gd9/H'dcC(>|*M#1cUtC*sO>ءn~Xˊ 8Ҹ9XN8Ϩ|y%GB?n#~ϻΣq75=:뛺x7 I ʈ +OɎ QyEf[9h̊>6#%)IԵlR2%9opqЫV*dxIX]r\rq?&''?~̘1JJJ7o껟 ZԩS&Fҟ ل t{eL?#k^w"?Gڏ +6LÚi< M yQkV^qЙc|>a1щYd[n([?y):c(GWZ<7eFGC%C}53H/=˭*)-(_VsM +^WTTcaaXB,ib#C͝;w)~H?_Ǟ ئ91Ɉ#&$dd)>9dH'׸s6"qI 1Ƶu/4N>z*45((WAfzu\=:'/{88SNU䯬ً$;W;̈Un] nv6J>F!cS]S!L2I"6)5|__}콧hXҭ'$$$H#GH۲3 +W_W>L xş[]g5qGXIT$ukVrZ^PXR`hHBhd2(_XWP$'W[S,L xu[I}C$/! F +\'L1xMWƍ6Rd8Wx L<3 - +B Fw=nwll;iphaaaiicsss===mm۷oں:?mGڀ_|U᜶$=aq*{>vs)jx^y,$?) SF[˄YrV:Jŷ{&nj^UUM{Mlfc>y#GHq+r.ъ/tsMbYE‹lwվ&)?NJJ~ds>G"DŽW̨ȘFCGw8{pGH[0kM=y`c~vgTL*=ذ撵 ^/{znoʷw*5^j hqҬ4h,Ysn }3P~(5 +ͨG5 /F{ OF0;A ]Q3&'+*91˭%Aaa!LKK#8V$ fm߭K U)J'lQ|:wLCZFu_.V9 ;AIȒBvE_Uɮ/+4.Trɔg85=G4]Ҽygƛ2xڜ l#sqYv1N_qs# H=6y-G>*=2 )qjkx}Xz.)nݳy>'q㸿NYNY+YO?)Rc;_&_oǶo?JjE}ŀ}H ~\~~{\r~#_N23(%=1.1$:>~{+CЉ&)=9p`ѻkޡlӕ9NNMms[x5q~hN vtg/91b}̌&>e-?*;Qg~2lG' +{ѥhWN]V5FEz~hޠbfFEExxDGlw***ˉ4|>#4'???$8sx!)g7oehn̵U-Z~=⳩*+7ŏ5r-Rd8~;~;6S|̴qk-C{&.\dj~=vJ)~Ԉ3'n9v}*MR>^.KrM^~{ N5Bj7}0r7}sY/bH:)_Dac!ʖnF^^{@CZMX;QO]GGGdzgۓq##ac |r/2x{^Hܹy@Y,~;+GQ+_aGG|3'):rW#Z|?l?PUs_#QdzV$iH&&4ڏؐf}3P+q`dkz篛lx샽uhiokܶyBw7yjQVQF=Bu#ZԹߘAh!q1d(Zf*'<@𚌔 bX|{:^p]pNB0_l_{`ЀoFyاٛq+_ Nt".G^g(6qHxr~΢bE!~,--mjeƍ3g֭J5F}ƭe~]ɵ\gzϭ̷81}+?LdƩV$a 6p#/^wa̔] +GJDyAH.|+x ,/W5-[};Z]g9TV|23<щaY2Cqqt?I%+HǤ,Д wm +co~!K_ l ] /Z% +ٳ/8Ǧߖt@u˙Iþ+joUPPasH $##㩵igE7гwոu&8 "w&_[C%Iw>~lЙwe=ӦMرc"rfk=cgn.%rl?6Q2#ckQgğՋa~V/^cSNrl+^G6Rdq)"'ZIs YYy'&xy%89EمX[ZXx~bފ3lޣ=sCKos[x&]7?꼀y[r"L#7߼i9qbakY.cLL73v*P <G y7+Vi+7׺q9:Η׸aHIIIMM$#ɤo%W+)2,dHgR{E&G2:2G,5hu٦eLi9z/_.|ZƎ?2fZˊ;#//c#>1տӯu.w{anh\o)96߇fĉ[!]Sc^d&&Y'azjM/5ɱqKϗ^sYrv ؠǽ/ݴxq``sO?~mj𡋞ݭ[6jjcgE?4WѼpm뀀/m٨+yśjj^5tY [},m諘v<["n^^iFFArrn\\fxxjPPw[c?ίZ[ ٱcСC 2xAu@"3 >ɓĒFtד?v¿׌)k32pXePnI޹sAڒe-9ywo-kt&eq*UǮ3׿36kv^mm9ʱ' 4ra`~]MCoyR Xx{n\]H7T%HҊ$6\Fڒ$ˮ.VqiQG͚ÏƍI 񨩻~[;R e] +a]au~Evl}}mܿA@31d2o޸چFA[vjrJ7ŋP;@kk_ /cc7}ϵihX]fqI~9f^k'/\7qrzG{tp_'GcNj UTLO9\̢fttzHHgs} ㏆|l,X_Θ1Z7oTWWWUUUVV2e_~z"GccOx?UȡcG:1q.*__|۵C۵kǐ4!e?Rk ɵpן(czdqBc~|\Vla *Ss"eYڣ<'9$~95 $|eG`7Ӊ,G3画}UȾf(c׿ + +hmmMtIZXzztjJnx@=7ɡ7~ҡɵ9u^~d6ZuttL3RCIZ9k+M^gch26:.#ɘcN>|jYigm(Yf#QȣZC炊i3}N:Uq]KX…G?c`)l#N*|'-X3tj=A^)Dy gOM?0HTGu}dcnFjCǐ|߫_=@ȨQlllZNs ?"ՑAZ^ѶI?6a D>#! +?j~0HAc??mG=M +?6qk lXʑHEݢA_Ƕ[W~F%ǜR )u ?"8!c;.X%׍܃P#Y |AAAp~AAGAA~DAAAGAAQ +ɐGyTDyed R!˨d@ "ތ!͐BEݩ,RAFT + +D*jH)ѐ)krgaT֟2S~D~8(2>?NHd??#?(#|~d0rV"#?#?Џ !RSj33?"#?~~t_"#?G6`M,?"#?~46{ -Rz:i? +?2~DG +? +~|ˏ -i~Dh- K&D2RZ$XJO/5Ԑ]LpX2$QJ Ko%Re.&\:),.\68l5)px$N?~x/\1H[H@dL} ޶?.e|Grt NJHwnqTaߢ ES / ^(PVV-T4B%" ؤ"ݦjiZ6m${IvfwMLsfvn~,ovr~999? +Oz8m/n96ss\&Evw +S? +B!W|>VfRXc? +/ˏr,JXvZ؄w\<# +endobj +93 0 obj << +/Type /XObject +/Subtype /Image +/Width 544 +/Height 374 +/BitsPerComponent 8 +/ColorSpace /DeviceRGB +/Length 17703 +/Filter /FlateDecode +>> +stream +xXW%͗TM4h"k [c!ƨǒ+*"(͆ +.m/e }3;[aY@>gٳgwLv?ElL 9QX$& xmYibތ zbƋLBzr&HS8&BvX9!TNʒi:d+ˇgEP/ Vז+wR_LQ&G 8Fgqa* M8,>47)&%LZ/hXՊ2_Y ri"URL=te>X& ȫҟ3QhS0ݾ*P.U_^UN›R op +[ & 4VT M8KtLͤٲ [,Xl77My;ܭ,[-)̬ܱFJH&DWet0oZRUfy20]'dTY 4Ϧ3733HcSDt,!`2!E2I3xިx=K@7_x/b+e**9tu!M<4}hQ>^GTaBlB2(7 2D.D+$=W918͘T`~H6Km! ӄR5dѬԖ kȮf1ٕ&/@_TNQӊXeKfQ&0ia"'d ם*Oa||쇟eC7o +$ݖI;-~:ScqrD\>oA˥$%>kﺌsk,E̅F ?T0{ Iڌ~$ә-ټSO&#bh0~iN-ow5 GTYU8Aʤl"o 翋:u*yhE+-9XtP1}Y{Sw'[*lzguC,0%]9{jgVf,ض(yfo~x$J ᒑ9-MÆ20rO/4\mg榸6/n{kSlVbyb"}bz  r-s\SE?#)ZxVwl 뵲wmarO>dwmΟ2lo]HynVSE 2WĨN׻n3* >yk-- bmD5d:S(}*B޲С˾!wy?b HÆtS~/vhɢeG;d$\*]+ܴGܧ`%j +1c۲ldXeAYuSū&~'\2ZdlΠ}'wӍ|AKǛ:K҉K݈PkxP^/ 7-<-ŇY\( |zZ":0]!5Y.4JwJN; p`\yEe_-)jJK_ѷV+'6o\̿H>ռCW|uχ~Tz +ȲC_<@oʻd2s^})Q2EBZV=ܲ+SSt6iZs_g,;4%HCJϴUܣb|VlFY_+ٟ.%1G\J?[*oDaiAyF??j*#gN \>Vߔq_ +~ltcMt6J&WE@lWsK;\2ye/_4.}uSk&VeجX. ?M.zd;э K۽/EyL~dXs sylT*R/ј&[u!U0oPWn>&nU>.ffq.3,CU_զH_JHOMe̳Mv#ho7=+C5f׷y5567L?Ȕټ:L,g +Ejet/IuJ 0d*),eki?#4Ґr-;~SXPUn]>bA8-Nj:S[h-~4W~N4.-^=QfRjKزx|;؏D(zGK7//Z:/~)U/k j>.\9}it*JL/`v=1%Uŋi_T6@WefI<˔T{YytMi|1fvyWYUM\pc:7~cY$}CWXfմd.2rk6B˾5,վsN6\hu^KƾL)DW[EҡecɉY&쐏#~Q&ORf.EWkE vV(_x=V#)?~-?T|,1xqUZY8Oln}?FOM<B~ɤqf>}e/e5J„Xwi収 8\x!rjr9~ZʪiU WY, v ]dƳ7F랪7c[EҹAP,5Lۼio%*נJUO>M^uXf.|cpFdqjW\TgDF0p:Ͳ66f9mStEqSgSJv_3QKz'si+ɢJ.K~/>cyO`֖?_]ߪ ŨB2_2~^ D7|a1It>*?Ko1oNqhJDZF6J>׫T'eLN +%2{^YcZ޴{9ŴLs}!b I7g4~w]g +I5E"n[Kd'eU0gqTo}d0='SH ʪŐȪJm^f둱!XzWQ +YTϢ$ܗ_x#[ AF[߭lf٘Q[#ʇm.Mޓeu'ߺx{Sŭ+/ddIz6i'SAx-&^PuE(zs$qAˌbT+HqߤK#tph`тyf9 Rp;1o>/Ww&/cYA"_n1Gb~:IgZEgR–DLZhjD4Lޘ.~S6v-Hr>jV=x/)dZОVin;3҂qF} Yլry-yTYR̫lRUU4HѷAX6* +ב~:BYTYtn%zoK$LSKq5R>@0FϤY#`%`%^g\t٨Aϊ*K-HO5 WB$2$mz g%i*%}rfX|@/C!2{sFVa_L|i/T"s/X^jUIUT^[ڪB=Ly5WVEߛ]nl(7frfJ7Z߱hou_cEɊ +YUyuAՅ*'ieҷI[)l>-g^ 띸!%8$ɓ{$[$O2IMegOc>h+``ÿ]WF1/(˒V+ʈ_- btHJ +a ze?_NCNq +zu0Ӭ?&}hnZj)g&1#Yf~2+f 3]EƦE~:֍V)ԻmC 5j1tafo1쓨aF^mͨowkՙw9K_K۽~[bÌbZ-+ִ>oA ۘmLw +Gh3[R,ƍd/~1W^2-ys#-Wv*%oCŏO\o&}"3S +y) +:dRJD[fBqy1yTuE#~L_%yHha͇ѡ8L3x&Ȉ,R+3!j$i4 OFrHo?Zϫ<$>1ɀ[yaJm:coIl/CBi(0͈40 S0>m#em,3 bpx#ybyxm/  /         Q& /y_AA_AZ;w>}Fq\XRvAƒY.wzdžsLI\DTdݻJ1_( +EO\ݳkСCr9z +\~x< k(~H$}}z +tύ|^6Q5 L6nX]Sg[Nsfr/Ϗ#P*?\&ۯΟ,M%]w>ޏZBg7__R +G1GEI".)~:[HR /۷?t`q~"% r>hG6+MYuHt{\ wV1FvTP헧~bղJ =Š}b bѥ .s.%$r҄#pO4{$-7*rb\?*iqIe> CO-|o],M}'Vv/ } _!LZ#{Ȉ yEGdPa%fLȴ]RT3_ژļ~ׯM nVn^m7zP⿹{Ɍb .~}_Rg_b>w_H~INrovR=)ǬӻG$JH^R/QiW^HRy(̰ktʻtoX"#%z^ +,`v$%F$Ǥ)@" %DcɄLk%HW/J'4Tݲ4_X~0|w]=ǭY4mM|ɣ2iss.a/%W2Fi\OınSY[Үb..ۄc_>a&,)46#ˋҎMa\(HJ0EUBk")S,_WoRQt&QQ2<ۯJ=ϯs;m {;iY۷:D$b_/Ro{h/Ba>$P~hіLȴ]RT{}nKy+?^9⧻%OIOxf\%/5"-5N_(h,iV~iJ'~9[%nrf?<y)Q~܀{ӧUȲ;e%N~(f)ybCǡCF/X?S{,M_oe{׆4B_tv/?7Ągω_/\zvFH!y/3gv%<ϩ% +Km"9urSQȹp+*D9-v2|___^uwQ! +\6m27sݙ3gZ +EO\ݳkСCr9^ +O?N-\WQQPbEch + +,-qO]4 S +, ~px +' SHn7XJ;} +Z&>/5 +~2Q 񋱎1~ky/ Oש_c_~~, / +y. RɌ$\R$+RmJJu*(.*(RKrk6 bTs   /  / I2QxI|WFDdRڑu_A9Y$_xtA R!\,\xNdNk1;_؃"Ο\G~!#&HGHYX/ +0?7'Sp Oz K9e4ͮ.5?Ӕm^5XïgȅȅVX~OTO$-Ga_ȈE.*Sd"eeփ_4>L궶1F#RmTrs|.R.kFʫMߒPLC$R\E)gB z[vtl\2Y~~^fVzJԠB]`pk.ߺ R?lI+%#;K .#%䖤1ttlXn-cXkRi^$#S ~a eOjZ?3EAW! +2r!!*aŒY܃KU%dE'4x:jP"M5_4:S/aզ.8,5](1j,K(:q Yj{㖬U_@?,_Ngd\\ˈ_:꭫^MD^qɵTNO;=oR۵j:5_81.3xלQP׸TԵ%Np{`cdBސ\*0F:,%SP#N&5E迾~9ymR({f.}u~iz?SJ.#K887^HH + s RUhT.ўE_.)( _plŸ㗺8Ua~icK-$ 8>{EDW;K{ʜBK8/: kj~113?@a~",Ǹs4ri- ?Ga\DU5NxCpC`_c= yCjU/4V gd łR}y((ȫYò)d/{(=#<,%!rq //ŎuXAKn~~Qj/L򲲳&[ +!{AR@kOqG/*__dVĊ*Т K( +' )DW/_ R~Ѹ,׏_\AF.D.JH||DFhA/uHuo kbD*Kp\cR!La b>k+%#;K .#%~A(~t@wKQ}h,%[4v +'Xu}= Q Q f\*! [<~Wi _?j*-P/ Y%2AA^o|Q#T[Ӹc>ӈ SS +R: ԭ_8実ZTryY9OF1ŀc}89>{EDW;K{ʜBK8/4$ӵ<5c[( F /s.u,qA/ u/?QzFy!yYJBN^_} A/F x]>H[.1aܜc?u?;. +ةc4lC>uQ|j y//9I" Q.9endstream +endobj +94 0 obj << +/Type /XObject +/Subtype /Image +/Width 544 +/Height 374 +/BitsPerComponent 8 +/ColorSpace /DeviceRGB +/Length 16734 +/Filter /FlateDecode +>> +stream +xx*W6DT*K#IQ4REP&!BH!%nK6˖lMvgٙٝw7_/I4063$M4<]X4N6#-T&NQ +3g=A)id꯹ +Bnm[δ=?KyzR'{JgzHOH1kجEIfO>cԀ6~]B~y>/dQҧ֬ѬHќF ϧHќpv^%*+gIwE H݆QSY6+[O=z'ҳ+ugIҲ*WZv^:-3'g%k(2KM]??gg-'[8Z6otP#fg6Am$p~yI&EE'E%l&<72tx̎n/ ua(z+i[?-+UVQy\Fex&.W-ک0[~݌'[ ټay[5Eu*[`lpt}{d~)sT^zXg~S ~yۣYdra&03N8]D/sk_JݣC-դhV.1dݼ43/ǔ:6V ȰoZPBʹ_h{.6SنFE(,Wa?7b5P1C挒'K "_PEiW$.E2OaSөDRXX_vfsʺVnk k -~Q5izgߩF|eEN̕;~I|a4ūK^,~f/klBדE %sLx1 iC^= +f/J6iEM:MsYrL9WCU4+bRʫ,= ( +ȳCݮ̥Uߥ{yMKwW)㙋BK=/j@Z㎾\M3Շܢvbkbbm煽L_S1FCn_ti%pfGƟ&O¿]1[蕪?{#|zCA"%s'UUuF=`Hϵn X\TnNDN&59C̕tFIӓWs;j{*O=b~~۽\R%<o6U^zj®4Zo߸o7r&_|BoZ{vЮ +ZꁊK vG +U\]. ++7M7k(H])fv!3C5_9-j3X?z#3i}Z;xԾ>Q]~e*o+`vsנ`pY}TŵÜLkr9\!JM_kRR<{ZV }Ǜ[V𿵌A\40K>pbZoUkUþQzV[%9rv U櫞|vٙg'Jw_د#:Hf }1B6dyä= Z?˲az<9>Ϟ|1v!3DX_/-X_Ksb0#JrQӤz1wɻK'큏yVQ Y7m|;DF}m鼹[3{ ,:ǮM%vtF"zK6Ukb*%eZPUUT_`|zcŪVTW=9WLe{E{X4&?~Mi1֥[h`sMȳOs~"3xEG/›5Hub=*!(LvҌO 7@]!5CS~Wݸdjݱf6ψ .zKTur!k(V]J/zoꍯU//U?XBTg36^dN:s\Wɴ~كseG)]&~,{SYm?^o_>NIB󓟆jW6k﹓[7-dFH(oX +δK(*)jWFVsOͭVo +C̕ӞZN⽕uQ)1{G}1s{ˉPO}t\E,k"|uu׭oBZscKߛ++EA+VT^Zy] +UeBsTOkIm:AgS{?1=$z޲i=dwΚnθ7j3QitkM؅-dU)>] Ƴ=:z~~0HKj8rhJ%ȘlOW +_2DsٓDZ4h,JϳLi2#4IR6$J4[hnh$.|&{f5Pȕ>m3;zfihv[~h!좺q J,V>tK/Bܼm^nFZ#R}rm&ɺBmdNp=ɽۇShjT-J`MCy{TƢb"BwG^]);+B~.^_jX+S TUky]#Iaz%wo#g~x\Wq%>(AY2{Sy/qƱo7 ϶CZJ=%\aqi7|KtEQ_8Ő_cS Mfk/lT_1&Ssx.z7;")l3.ǩx4Ǧ4&qe<7.'I>QFH n9,MH+C~[ݞ *o/y)}ZE~.31=a󘮏~`̻DkpH9K ~a[{ b,3̂9Cۙ:? +p}7u#H}K B2uGi*GhcSMiH.?._wy>loNx3Z/:KKf%kVBZ9 [u~!x/ Xf_n*Fkbs-V1nk+x +TXz~3sִK̩H6s- 32&J$0Y*A~ɋؑvnO[c[f)JQ~=C~1h^XD޹>mG JQ~as2 P +! ~ 0C7BwUc}c}Ԫ$Ergxm[YD-`-I[*3ۻ}7;wm}D;oSk5Շ9$Đw} ?*BN9~WyB/:DEK,k~?F~-W4 FklHi%l^Fk\H.Qڤ"A$'F&/+u"0=ya~r+9칒ש'6KN'~y[Yl%q#}?С5kܹøVyН.|v +;g=vMy)>_|5"5'B+N{Z}R̬s +ˬi{}/ <:/7|j5h#b+.%)12%vZ v2 THq^ fo:|{7vnU΀_l,l玥HvWE.bR~K +337^}7nп襋;o_]/BvA m8\({(?˙HB~9Tu돱5-t'C,Gvg}ډ/,N p ޽kVHK~ɪn^Xk^~ϵҹ#w|GUZDp~O`9yBz_"e̅xxh?7]ͣcCT!R䗎;f]?y\Aʕͫh۶O> +A\A~5 +.doňa{&^T_C o +v;^Zp64wѠ;/ +6y=d1~[T{. +x9 +ں4~ +E^Jѽp%[~4\qC ,:vX,K[{-(nYXT/ rP@E&!–~3lO'o6E +ܽ>íuC}(ɻT0~11P + i6/ +yIQAWvMһ% +EqqQNnVs 5//BaK"[׹_eO/u5{L,xL\[r.TC%tKqeP̶օސo_Lvj[XZsEx,ht^tr]''uRH)--.Z.aP͸xsu$ _%I~܊}/8ս~:1b㞄_ʫњ\U̹ +.^!B"P^;38;ccOX*s|L}sM󬡐Z'sR?615!FkNEjNV)t/6ٺ~J8/?O"'[?|}C*?Qj^A9Z#T (qYTw劥In[ + +i|X%v[ /p#1d1q]`lʹP -~A.~c&E;14cKH_t<&cnTsJp} +] M/Xkoi]!Ks~*l2(NWI ?F. ο R'~/4׿/ִx`>_|cbZr&Mh_//@w_N*ٟE'_|B}dA\/Vs_~ଊ󺩼 εʫZŜR~*P R_ ΒLx~u~_"s˱kK\Ԅ99Zq";_q_~p|~s(P.FWPH.g" XP CG!e~a( e\N/o~ 1S{lP_P) +TPs!g*AG~~A0 vΞ  aj~AA: 8 |A ο  8/ ο  r_AGsA\pAqdžI848G`؅  ~H.o\}3"QdA窫tɂc7_?Zdv8VBDcqBlܝobgZ}·e]!|;?v}`8 xw/" ]> +>> endobj +112 0 obj << +/Type /Annot +/Border[0 0 1]/H/I/C[1 0 0] +/Rect [87.6891 122.4424 94.6629 134.1483] +/Subtype /Link +/A << /S /GoTo /D (figure.1.2) >> +>> endobj +114 0 obj << +/Type /Annot +/Border[0 0 1]/H/I/C[1 0 0] +/Rect [462.351 339.6948 469.3249 351.7046] +/Subtype /Link +/A << /S /GoTo /D (figure.1.3) >> +>> endobj +116 0 obj << +/Type /Annot +/Border[0 0 1]/H/I/C[1 0 0] +/Rect [506.1364 122.4424 513.1103 134.4522] +/Subtype /Link +/A << /S /GoTo /D (figure.1.4) >> +>> endobj +118 0 obj << +/Type /Annot +/Border[0 0 1]/H/I/C[1 0 0] +/Rect [54.7104 63.2237 266.4741 74.2671] +/Subtype /Link +/A << /S /GoTo /D (section*.2) >> +>> endobj +119 0 obj << +/Type /Annot +/Border[0 0 1]/H/I/C[1 0 0] +/Rect [303.2097 87.9567 435.9505 97.3713] +/Subtype /Link +/A << /S /GoTo /D (section*.6) >> +>> endobj +98 0 obj << +/D [90 0 R /XYZ 54.9921 757.9346 null] +>> endobj +99 0 obj << +/D [90 0 R /XYZ 54.9921 733.028 null] +>> endobj +6 0 obj << +/D [90 0 R /XYZ 54.9921 550.7846 null] +>> endobj +106 0 obj << +/D [90 0 R /XYZ 54.9921 550.7846 null] +>> endobj +10 0 obj << +/D [90 0 R /XYZ 54.9921 410.3766 null] +>> endobj +111 0 obj << +/D [90 0 R /XYZ 191.2651 185.0865 null] +>> endobj +113 0 obj << +/D [90 0 R /XYZ 442.1726 397.2535 null] +>> endobj +115 0 obj << +/D [90 0 R /XYZ 442.1726 180.0011 null] +>> endobj +117 0 obj << +/D [90 0 R /XYZ 55.7066 77.8533 null] +>> endobj +96 0 obj << +/Font << /F44 102 0 R /F45 105 0 R /F48 109 0 R >> +/XObject << /Im1 91 0 R /Im2 92 0 R /Im3 93 0 R /Im4 94 0 R >> +/ProcSet [ /PDF /Text /ImageC ] +>> endobj +131 0 obj << +/Length 820 +/Filter /FlateDecode +>> +stream +xڥVMO@W1agV-JJ9Dń$|@_#ny/BpAPڊ_D ;K JР8JNN%+F\\.? Q?f-٪=%aK}{=|_/64HJ?N>*r e1R{L\$TF<d'#x|d oUmюKIbrCق`JJOF|xa[B@[GI, @҅v=*\>N6>^QT~M%èA65e|u_dzY*t^pK({fu@(STA;5Pq+M94ZZ9Y 9*Xrt(bl"t !n| _ViB00GުKP@[զ=J-y(#BPm B]A'r>=`.s~w}.J9*)lFrn: Fm +x;l.ZDl( 5lF] +W5FktɢDBg=]4gؓ1p.eb"# +ZhEBlBWBÅ(t7=hiC!?|ͤ vO5Am_7Eȧ`It1~Eendstream +endobj +130 0 obj << +/Type /Page +/Contents 131 0 R +/Resources 129 0 R +/MediaBox [0 0 595.2756 841.8898] +/Parent 120 0 R +/Annots [ 133 0 R 135 0 R 137 0 R 139 0 R 141 0 R 143 0 R ] +>> endobj +95 0 obj << +/Type /XObject +/Subtype /Image +/Width 608 +/Height 434 +/BitsPerComponent 8 +/ColorSpace /DeviceRGB +/Length 35413 +/Filter /FlateDecode +>> +stream +xXIǣzw;랧g=Y{+vDEP(H)iJ{ #5lCLBLB +‘2± +sU@S ԭ@ܙ|ڽU<%4H VDX yeXJª$j7 cnPi}'ԹԹԥO1ôJ[@ ~@ xCP';a8TsHz$" sX8xp2(&aB%K\ p1~1t8BqхiT;P#aY!IXEUH؛5\\#$I#Z1 GHSp?:q8j2? ćIt@7@8}p o~yqĹa$h"ZөcE!H +r-p$K'+pV"JXٍ:W@ PL5SUNU?^9TiIp-+iHakƆqQ4{ {:ŀH:%;uK 8P> +_LQȂAp ""J"#"ҏf5QؚnH_zqj@ hk䑒ϪJ>Ryt(JLtFY +G?L\F#0 =S0>Nbb|3e'>>!#ݞ؊> + ש)iITY&y 0-ii8qYk\On@iɮBd-"8T]P-T\SkϬ/>21흓/2-gß {f$!DbpFo cW G8P*DlK6Nt6~Gl$xO},*G AǡsZ=Z50k~tYѭ&FSO(S?$K~NHaɢ7zak` 6;"!>#"檤MrPoÀHG=fN kGA h( 2ړͷ^6]5li:A9py +yi6X쾥 O@2)X.s[UVP>[.2}wm+ۊ*:4e0 )\w՘<ia  /J ++FC7 :/B + Jf[ÌՈv]}k3lT ic\;t]ʮ +uWI %/f\$M@z4]4bc2ڏ|̙|cɹŰa՛NZW||Eѡ]p1p3E^2dqFÿ>|]n'_%lxMꆺ/V;~9՘'{PO2bc,,ݷ( 2q ow-*oen} 20ֱ-WWP|D-cU~F!EdBXɬ5xWLFQTr 6z!e%&yҫYH.&] u4oLG̙lc rQh>:-킣ȤmLw3qW!f +K}GH5sҸ +Qa޳~vz}]}m̓7 `lΑ;Q ffmPy݌O*)c#Sr*B֛X|5)k8㵥q +g%35:@BgyzyҎ[dwɻnS6"E^qy 9W)HR~'t쿛h|d !t9Lzg֗^ځX&Trwϭ=}w^&K_:WYV`'G81>m~"u)م8nxsx?8ѩ8 jy,CE;G& f*gYߑV +/I%\1-T$XPxr&A V˜n\qYCq %r_Q~Hxi_7MަBty-[z&,s&i3&XQrasĎ˻(]W+vZMc2V}2N>"!+GdHb|S; IF.ŎGOWx1(֠#@$JDmmlEb˴Uu6a}|'t +}wivͰW:N ?_KwgwiJjDڙiȿZmt(d͖3ӝ*S&h'/S9OQkh_7lI|y +y-򿈏3Ag3AF~l2oøC O-^z؆ k.+?oY]}oҗe(,M +qDbmeǑU?1lpllDRks[uq$ux0$¹v[ h ?Z)jҊ/qq"w0g-`=uk4ž ;(j7@cF?mݥe31BΑDt LI +#KŌXN+mUw엉FPiu_ؑVCzLWbPZ96ѺˍQ,멻y*oZl<|Z+nҿ,cZ$=#4Le31 mﴗ]8Z7Jl Y!26A 4vnG*q{>NWjt5}+eWL?3at̙k&:)Oѱ@7 ֕[SzvE*M<< S<+6[##JlB¶*lG#."!,~.Uvn-^|- !iDJ/}9  +ȢW4o@ ^Ys>=Z;LlHϕh{W&_iS1oG6?RG)$,ЍЌEf}=? s SzŻ,k1%)^9:N>D|W?#H|bS$GΈ3uwl#x}Б_Twj}oe +UO%5Nٙ G +'+r<ұ' +'(w%?(sۣ%_*b_ 󱇙`Fh[ɇ-bY{ &oqR¦'$mԵ?۴%_EaA =$Bc=фĭQ̔n#Po苣eЇ{A tU?@7ܿ_maט ln7QkF +*^Pڞgrވ!&-m48~g;yc^8;9߸Y_L2m۩'Y^>; Ib'%qIg'M8l@A[<:dT(mO31ly-=<F3nCpG&>܍a#;|*w?qqA@u_C8܄=>ypL8D<%q]k#~>$&>b'!>aLi& {2N-m @kc>IVۺi'rm YWK31-vߟѐ-c|!NI#`gHq@ }NƏ^x͗s+2@ "l',"L%Q>@ |@ @ P@ @e3@~qh oHAB6ga?jgBdʖ@^cB}{ :zKvPO>,~ %# `?jV;[߅spcw?M[&>d|U f+?M򑃡!'gy|א^#1{88_ ͹[9߷p^U|@G2K|n*p>>'{Sw=`/v|@GA5J[x/X+[ | @>%1cwX8w=݅0yZ$#uS >P| +t OA ?|\C 8z~4R>@ #@G /l A ꎏTp>u@%1%\Z_q)A=a fF80p4na8 +;}i!>AGQx{h'Dln @ǁ[W7 zcze u+p>Ňq*~ +xw||)mhRŘt¹g\@󍀖5dy2=WB|3weUo!%gd [kKK2(@>vwǾej?G.ŖMͭ&tLOMsq sup21qUOIK^]]G,aVJRKC,w AjsDQn뒢4 iI32̛$ў{>A.^|oJApEe͛75wh9zΣG%6Y6h|h +f\::G=9㞏Hj~6gcx8轿>eu}gtD~_)KucZwXo~G1M b& RTU7OM}dd|ARjqJFi\RarzYjfeRZA#u:#㐟^^5Y-+(?7svWJWS@ku#Ij..1;8=VXtRs}o2vʕ>lcm|[5qS{<>Q.AsQ6YN/D>Qs&VE%JGCJJMmCMd1ҷC[[kkkp̠9'G;ΖHQ&D?FgO8qL1H lgqNYke_Wʖ۩mlp>fGI! ջvw~b_SR^\[la|Vb\𑧆qܢX8:3?CSE5% 8!,*؃1Q=1&kMvBKF(&8Ξ=|`/=zcJلYqʕwbcmyI3FEuc$#L;\ ;8M݊Cplim4Q>GfEdbR +P!^AIylC{{y)텋ĸu0!`dZ@;Z^MNMoZ\iy#sOWIXqEýBIp`h^S3J&w񱴔W]$?߷El.--JMMt+-JGd@#=whmj :]{ٕ/rUJ@EBvF>wI_W[74^pƻdŔ^_PtxWTڀ 7eZUohwJh ?7Qzgdu?Z[w/i&YR&4;j&>uKH65{lDG8o2rӅGǟ'Lx_)j7U$V7_U3\}S/GƊ-ǡîUU29fIUWc;Ա)Hlb^aUlbnLBNT|6| ͮ'}#V6V4V~h/++-)-(JH~//8}P 7+++QPcQWz&}G˗l$Q$jX&!N>?M[.D"|xr🶺w{zz{{;88899~ٳgjjjnnnozu'7?ߙ]79gUY(bK/ByvAGoRGV#oU⯔qM KfZD|`Yvŀv6S]ғSλO?yhYmg^tN,.j,.k\ah*w[[q1)'K̺Q]RBRgAs!ѥi5/Hx鈏ZouS=3{{Wh_:Aȩ G 6a=lMDn!)^vnQ.4&`L2Rx펅3Wuk8hNr"/- +1!MKtԌ=Ԩ$۽.oۧpa}Z^xM)/WNGnll=ҼS|V=&U7~]BeS]&FH$_BmmmMMM=8q1!IAL]nO]w|3b{:{Q>3:"'|d-EOOe%:{ ykl+eE3#k;=K6 +4^zT4kǒ%]>l}VJ~7q?#ΓeLlՌ-m(*h0GYSIYCJFepdy9?]{MZg${cae,>>&1Yv445{%V<4I^t7+;ENO4{]4 UWu0( + u 5Es̙B] # Tug}SW}m䷲ag\UsFmKxE&ۏGm冏eiEZ[EExTM/D1lȧo%:֨D.V/pqr~yVЈGȢDIdHUG=Wd9xDKpƌh+pckS\j+>m&uHLٞS6?9vC jjqjd]rZb2zO_/"`bdgG#7lڊnM*RӉԘjj`1%z/*N=@ݩ2BΟ&l'̴԰hB|;JA25/)}?-935='ąXK9s͛Ǎwv3yLn4)'寔WKģY;,a^'|ʽG:ʏvJ~R#V}EzVULBSqfV8"a=k69tM=ie;f+> I* )DpPkjH Uѥ&6ɛ_ v鑺;ov=9tYIM,mR|q5 ^lekoc.K>cmm1cN=jԨܘv7|h )k*j+iJ$Gf!r\ڏeYHP-YeS5^*>tI{a[",<#yNMC-!B$|~E[N$Rꊋ23RR""҂޽;vAmFS2q++8//oT慨FͻLM$] +LD=yo:Y&EJ +c_ٌ +jeOɗp>uq-RR+Rn3SqoSޤ=R7(? }K>GkἏMg6N i59MM7>B.,Ko;(oq>}D}x]XMhuHYZ}SW1>>~N58ycǖ8V[ٌOyO +o*mv%㫂eYW*紒d_*Yd|h)" ^vspDW6ЭHH艝[B$HIIccq8ƺG?ujpǶ6o#'K3Z.Q?ܥQSU +^I:xyE;:mwc3ӧᕏo&"*5jC5>鐱o5$m{XPﳅK5qח oϊz8ԐId$m M-L<ܴeX$ ءaM1oּŌӑ#GF>?[eI 69rƌ?g}|r_׎%z{MrfMϘu|pyjXʢ {v]VYuRi Eu0eÙm?m!rX!9!PHG>}“ҶK~,*-C@Xq#!(]pavV]Äcc8އ5OEH˾LUɾm]#<av6!yAO867zFU45PrYdǞ0_+@GFbem:Ym䧶IMRM2m31HnjH"colH@+|ssXNNEHHO{KCME;CCOsw;ߚ <<{NanQYYxA>aͩwϨ>yfjǧM=Dcz +WlPNRکT4f`NGUQ];s*''u-5iּ=+␟_PPPRR~r|+9BP4DF$77I 4 CRS[G MWJ۪i51bcoя5Cq5?[ʲ+~w珛|K6HYt?+ 8'ʽj5? 82c㓉T.?aq:_F?±>t=?dd59ycQAk&PD|\'~'CF#R"UUWiW͒/ǝyq ~wkcrr#^[jZb**C 18etSYv߿;zu'o*+ )޸!>ѓGJS+.|nGY 7o 2|四[ms] +nWifd`jSϊ$[QkbۊfKkhi~t)Mr +⓪LmU4d#ĵ3f}+G<m=<~wH3N)p"N}צoI|<O)اYDq#"8:s񱢢H/;;RKKΈ(>4|~@119IqoD~fk򥟉ӲMD՟DGg{fUK]:?}m_]~vܔS[lV?T +RUX\\WYTEM + Ҋ2S}}<^VANAYq ~~qӾqQ_6 gM}S_*c)Υ8*}HJNݻHwP@_++/sswcc}}Gmm'O>sdƂ#c~]ORJF_:4eeJMÇ(ɳ˚晏q,2Ȅ̏ɦ<eoK/^ꆅ5kjjP[*,,qqqAD LפA]=򠏽RFL>X=|$L`)IFFFv6&EI HmNo\?jȵ']-B3{t:*دTr\{ymW ]ש s-=0?__ʺ[y'LURR??EV ?k×|xejX[:_/b{ؽ[~۷nvmf_/n5V]7n?mP7>F]]}؁u++72]$kDz6ZbIiUvNQbRVDT@GkGo{WVo_KNV;gk 紴XPP􅞇mfZ8sxGnZ!!OJVRdoݞx +!fާK?yOBIdUG7 +q>"ϸًPŋkyp}}]Ý;a6$F+ NI88D?KIqWmmmGW]]on=^LLLTTԫ/*mvڸ򠏽Q}VʄfViQ7_9ؿeogagkncmjmebeK_YYz^\gWqbwfu#̶#̲%̴!L&m5s"c\bЩmY +dRxQ_yK~<￟Is/vuuGv=OC^n n1@s}?8ZACV\WQ5R +?cqpm?`  {~nuD`2DB#&z@08p;2;}iXQӀ2DBAS n8 D^qP#* @!+A U A >@ |@ @ @G*l ~ħF 5[Q_'(>M[>u@ WӹG##5 PQZRg`HDzdc-h:Z:֑pjI$SG 'qTQD@ i :N?ԈLBvD(D2;D&6QH?ѱSM2 ij"c>ҏ]jFj`Ǧ&܄&$ZGZZÏmxh؊ ~>R#얏+s |>#@zZAg>G#|G!#>CX" #88Ⱦ~AG#q0d% G#|>n9ɍ _>z@G&$#ww|!(uS67d3#b@F5+tܬD q>j$8pLd G.?%|?Ⱥ|䏉`1‘K +'ar22.8.82~6g/CX"0|U>ȊWcOv2WFAǾ4Boú{ɀ>Hpp٫|h 9,>"ac//ȖGقɸ "#2A@C; 0>D +%#|0M \w}GA#(=y>Fdo/g neo]Uq.,s  JmGqo_dz:pCmm>GpDri nH9a[%8TW٢/9HpC#(c3#qh@8"A P>lErdp>fB~YD$۵HpC#g: coۏLN + Ppzv9(G#??!{_px[pBhq[! +%Qmִ%Ee4$Q(qXg4#AyNr +%6y?8pBCLN > +~スqS\[=9 *;J;R +rۣ2#2ZCڂŵxE6ǁ+8pHIIqiiwN7 z##% % chF)<%r>"1-};NȌԶd ~ mb[c(otLHpx#D2-@>9mf/DJFK^1j$e'ri?Iv^6b}EiYyY2"3n݄₆~8)iCǞ/4GAclV уH$<0 +"@_>r +sPP8|ᣔIuD8TD8,_~y8aYʯR'.>8sopPInr{C| +džʪJ++3bi|\o"IQ!{YDFr+;dHCu$lRr3U>DLjKlCMnEptOlEƣSLfP#<1TJ.g">u V! 2s23#ӓÒb#|\ZV>^~ۥ/mI|ŭW.l"E)gւ@A4zD7{ŵb +~쎏r9WE;tQXׯxqW3-Xڑ-th/GNlc!(UFDv5ZOXǜ恎^ 9AG|U|4l9| Vl7m*l{Pdn8e4Vȁ|,e |mB>8(L} VȄ KmG +NnLnENhc|8W~9F?y)[ې3.~;~쥴G#,dS{n<)ص\Fn*>r_mok`?`B1G~l<~>~'##8 +?GAi?@1 Gf? + + +&G#؏Cx ; +#`?V%ˇ8݀# + ~ ||l\\c:77бG&X S @due=Tނ0~]uހcުG# +Z$9-:|@dwuG.H,6by>8c9D=ty5Ym{GGSt-ה'Ήcm;95mIGTz{:L=#6>%(y#O/u ?|gl)V5[GWwOgSqFEn?Ky%l|fXu@\K7) 0!><b P# Xy#+$긧{f>5~¯ok:u]ĵկT2zH +|P ϼpy1}<ʈ \(|G>"gu(G=oL~OB6iަMtb |&& MrICI%6؀!,Plv1^C +8!>75I;ni}۶rM2=OIP| ^_9yo\ ubU~dN>nhzB/j9tQ^]#K(z7L_S#k?Zr&|Z陭Nn*9&_- >>  m DWU-(*u +cLX絥o"T#_|~܈]8jsG#{R:OħsG$  +>e:-&4(|l8lOfI-D7/JZ꓏"3¼KR~=M^0I원0y|dB,<4C`k*mIes.ITV:a!Gu?۾']~mw?FLIxذք/B|IRU2$ 518e8~wĐ2]꓃_ęѯ'30iE=q1;^_YFS(>?ĕBriCwU\ k|Ϳ*#/7q}-SԴ#>L 37oT\99OEfq#(c Mɢp-~RQ̔7;lnS}c+fȿ<=+#fg=cDt6Cge~6/sѰ#> GeŴ8s/ z6Dr:*4'тݘE|4|isϏfE'$)S7rU֊f{eV ovmDEuj{a|s +2>rWV(5({3*u1(to{nhGC`|?Gs.# ~K_S^zj] +>Aʯһ09n}*Nօ*l>zFmDU۫g-Ѱ0ۜmU??.a{ͼCqUsҜEm=HsǀK1"%U 0NB`\Fe|;4'jZ׼$ȏwC;XV1o{܌ +G1Ѳ#>j} qAVKr,/8joڍ vn}^VNLȪJZ:9 fGyA(6ۈj[QU7>MecX>%: +w/F|>&/k\Qв>%ltwOԚ+eW{G| c"R8{$P#Aߎb0)ʢ5[_<6dD?'E,5a&OHT$6ɔ +>W5<_FI| D%|m-m哏ْi|\X8uU эwVl=Ǘ " +'MJ_;96)y_:?=E٦ HsĹ_|[Rꁌ!ǞKDVkwǍ+Ne_K>3~g{Ǿx%aLaœLv!{EVL My9= +ħ{ȘݚkSB-c VV/, +4!>H]8GLX섥0l +rDC)-nRe##"d1k~(F| 5>vv^q W_vX!GʯS>GP>DMx~/{/q3/Hh + 'rB]3J?1#JWf&k~CgO66+4l/k83q1o ab{ʩ'_:&r!a}~;iFdBGnJx;̣v/c޲=%[ ߼(MjO>|~o|qwYqN,%ro|{}8Et> +V"!hmڅux (G%||bf3Fߓowc#ǰwhNHČ_96n2z~_EkfWlw@%,$QO +!|ݿ۟e~C_u"{2fF?6?V'.԰JsrfreMޞWc˫Va~9&|T>1^G̿np|$23}xdȌ7=Ѻr#k"f>7lG?*o{<ŠS-:~g1#?3;smC||r#/OńLشO nK ~/U!Lџ +]$fL}O.LZ4;61:nѼɉK?"#Bwݞ+'Z#[kJ +>?^%:a@8SGlCSR(:B?"G/RU uK<$xa@8gU +!xL|DS>"Q;TBQ#|Ow>J{rUg"5g qWDdTĥ"=֞[ݟ[>"TKnn^|4<G/?8|5e>8SG1#>"G#5ҁ##V +e04Gp* ̣OQ[ G qD??-ZRRʘ%%:\|ǀT*pd|nG1p;>G*|ǠTh$%-ATB##GD" GV U\vYw<HGGߎHuORE(FF{GQE>TY+͎f[YhQlնnHu5|GD  #=y'e*>"^ u,Ty|GDzq@|fpdL/i +@;1t+GJ/KR#,Jt|ni1ɨƄ,#|D2dzw[KA>Ѹ}B1 IS6Tz='>![~5DÑg-:)e2$HTSrQ>z>D>`p$(i5UdϩzQR +gפBI"LZ(6*~{;fg%lq(.jNs!#.GJX$AG"#4Dg.kV}U"O8b2*3-Qh9iWҼ%G~߹oy۫fTƦW?\#6:cBOn8JvGQzH(dw,ΔhJH݁nAVKr,/8jo> vn}^VNLȪJZs>"@I],񙓤P2 (Ra,G [Qw/% +mn&:ldz[ZkG6՗5W[c}"|=:. 1o ab{ʩ'_:&r!a}~;iFdB|tUr w[KND6JN5K\*2sj#" +#_E{K6\^z\dAG/JR>Q4 +p?gfg'?owc#ǰwhNHČ_96n2~*=kʒJܓcme]n[˺vli=mi*:< +Q-2f\$姾G4RS#_!/Y~qMA%\GXEt>{[ A))IUu,i[P㣞.>"Rܥ!_D=L,z#{m;()BcZf0sM>"B<%(l'1۪*~8Xls!!#"8 ws18C(ɲ2HRfHu3 y':b| QGDpQpc8B JLe\6K !G +TpGG#W3!|uLOA,DW[uDCJkX? +,_6.\s#qhP#Bc1;!#Qy`'wrFMp@VUq͍H1=r>uTrM6NEj%è_E͔$F< 蹿`Q{I$S떮Tn7ryMqGc_GGBF"rqc|Db|6`1YI?Q eo)r0z[ELZU\F܌"V,j^cCAJ[+^srXIJQ +_:j9MKrjL,~cF9@qmOs1iٶWe +Wc|-\#G#(mlD*9 |GU +)>-9 +R}.Z B,?G(܁GR:U W|B0тQ +rfF DzIB#p`dLP5EJ"* +Q" !G7MITRX\w |_E(ї`\+B]# >B# Z--0,ހUYc>kG?h`hXV՝ +5>YCre>X\B7#, #|!Q!RWhҍ>bC=m#$f"G}x|OցcQ*46"#>S(O_ #>Bc8PF(YƋAJCp|G +h=]G#㕋GG M#AA>BAAA#AA>BAAA#A~Ac}+ Ȑtɚ+OVT}̉*r9| :;00 +UtO4mYD젡[Bų vreG4UeY + 8N\ +(^OKbwG}7GզQ KBBs|Z]bq3#Jgid<%IܟXDwQQc<7(X==V +N>v Im_xǺ G'tܻIb [i|yJX+7.]ߑT>Jm%rG K'G"Ǿ^y|>} +J/d_tt[[[x^p$"pGʛ|#>Gݖ?y_3ؑ#_<:ƥN>| #?J5w*QKݬM]Yu>J5,|d{*T>}sȇnu2_;z.^[;o%ww$ϝ$TVd;II߉g( +mϖ\l4b2()ᣒlKGY$~9>^HKbZjw]]r[guhW!&s|| +|U +&#L}C9p$+䣦Urvfq81pS|:;rh~.>*6Z>B*YWٛ@(i4v|>msctgο}n8rժ^pd#c*l#->n9>a>ܳbg8.]z/ͫWut\垓lݸ~61wy M)#%ÑG#Hp~뗏Nq6nޅDd/;E #lqJZUV#A7;G}.lϿ*8a7o;)6iͧΞ"4뗮\n;w瀏FzG]Sׯ]|3-sD#A5zA!~a~+ڝ:/(%':~X^& \X/ⷝ85e2w5ՕRXwvv~ޟ%pA%i~9֭KUi?4C"/9reA\W/~dWjzFoϗr򸳳AA>Gzw_:!q|  +8=|G~Z̮AÎKy]h=y\4=s)g2L4Gݙh=z&5L ԲinЯv'\ʩu_lhuR<4=I-\{\'M~$${"5Aڇ9MA-?YJulS'[弅  Cendstream +endobj +122 0 obj << +/Type /XObject +/Subtype /Image +/Width 1032 +/Height 746 +/BitsPerComponent 8 +/ColorSpace /DeviceRGB +/Length 56945 +/Filter /FlateDecode +>> +stream +xXךvo7j,1$ R&E"UzGz {6:@pY74e5y{9;}gޙ}sΙA`X`X`X`X`X`X`X`X`X`X`X`X^=$ |,3 lҧl4l7z1{*l/!@ sH9 ]H_(RV˼u>]H +K +41@ @ L1N#8hS +e=b}-}{Ԅpa.b΢ѢзxwSqqku{L:^ 6R +XHYhpTH +|0 #^;*{y8R""oQ TZ3H +;T:0W3g:Ku[XY\$ZAځ` 3{\ oF8:'6ly Y An?BOY׽6+g͘E Y'r2YrIe9?7N5ycګﯯ`O[f_{t~Ȕ+.dc)Uf@!diX`7O QgDF +'6dj$6aevy92I6Hn~sn:+ߍPuPo?N*^:!u,f\ߎނ3O.m;!|7 l@a.@㼸)Wц-ޗ/5˄ :x&| ʺ{f_ж r3V&=o!ζ@J->kFꬼ.diY`g`O q7ݻ#ikwL~k`Y >+ϔ"_WK0sP_7 +,0lbF<\~d猏V(Ё~FAΝ4[왶#@ g3GīGK!]" <"Dyi ia00<>svمσ{ 'Dž8 +iݫhb2ZR+edƭo +RzmǤIG5I{I(7(|@N۸sdrFF`# +k.x}ͮ[:6wn `׏1b`v%v٥.uh`F]*=c?PαcKM+l:Skٿ;u?ݻrDzm#qciM +WLOs>rvGځ.@-A,_v +l=KV\j]~ |RJ',@S^! π33')l-/9ڄ:B >iM"j~Y,J,iVS@ @=^ /<7@ +#f]~C1vK޻@ @ @ u@ YX8A @ tX^ +/E5C7>F$m3(,k)̭/ +ʋ/Lɭ^8+ZsD{ ! +[(2J)FF7HnĻY:6Ġء +ԽMȹEٴ=z#~R{D0b` +N[ +LsKs .,,שׁE4`˪qUu=ff6LCR(fJ4"0(m呢b`dx_xOpFHnqkaզNZiwQ 3:Y + Dme߿(2*C䠜gt>uA&,w)4Hϡt/&f狘eQN͠m_(t6㱯:ocCc:Z[kFzZi +%lw+ +n8yLm^un$nFI^rE w118*>ZT>}a#~Zߋ{9%1g`]WToPZ&zZDTGU[Y577=|``F矿?<  +2P0#]P_pڷU4ݳYܧeҳl1)˥G > +CXVIu{YM'U]\>)15K + +^<~:پR %oۤ߁^_IcﻲVǏK3! +ªdRkI\#q8Dz XbK}4*-5bMMM奥ٙ774i0[#,Gd\hN=L˲N+h^{AS漰W9o.{ 1/S cQn? ++xIucQ;G;mδ'2a wzzi`;6| 4H8}T=X Ep:x=C5gcNae;oxRp[] +x|X|GTM9 b+n9n{Ӌڮ0=[TĘ%yoQ+[vKaVb*uXCCMrV p%|vϢB =xM-LKDUZ;$6;&:%;'k +nt?%ؐ˩|?{yM;GH-VMRJ SѾ!C.;"WpP:ToGDd4X3BBBCCCݝMMMcccS$뵴2:@t]-}n|@}Qk׭kcEk1Zvs Jd ӂGSw2,r0/_oyYfԜ|Uw\8^u +DzFzFѸgM'\eG1j|dg0s*˿eҶ #O]2{k21A)Nrj=\֎Be=77I]6no_JFM-~^@bDnylb|}gG9/ +ʋ؇/L_yZ$1.^AUUU $Sh+]YP cuHhtNjuMiwKHs̡%hBa-x$*i:@L +L}TJy6Dz>bbbqp*|Fv.X#{qu:OrlJlDmbcKq6<>uzKf#6s9TTp*Y4r<*%]JArgy(N szMzm22z7Xgjhx24y5%zj6K)*#y:>?)fK`-Az/GCrC-7p6"z\1@ĘbbbV~}+OHC˲"," +_tcIŸQo`|]|cRkJ{ZWFO&iV;- + +Nrݶ4tX.yIl rKHִR2P1S`L58H +xZ^h+k?Vә?ߎpi~W:n'\y쬀ʌسGT8i +2 l? + +i=ݭJ" + a!P~!E-XиSBF۸i˞˗/g.`N Xc)dSOanH3<&U:F9 + n, ʹ.pvKΩ K, -T-`heP[ꑳ[~) ;Ɯ>.@q +}$=]|BTL< .%FJIfBɫYgahkp_H +ܳ.1UdaRTZc +v6(ԢDprb_P +zLV$> +.n~_ Zk$jnaz|} +*HnT[9ZBcq26*3nTؾS .yF>;0Ҡ;A{W_K{Ӻ1*62$8&"z3/2?#>RI=Jn`ܾ}TL_BD`I4P$ͮ(*[J(]=8tm{Kyc}1dY@]R(]cd +e +V)V%| +O +4 5 s5NV~P告OmL8@&*gt#vY.#GZrA3t׽sGҽ ȸrɞes1^csjӉB0ޗN-ݬ^ra.[5TSs( q8bqE_n1=Jl}h4Yc^'dL`3 /rSC^@ ÌcqUsfP% +~ɜ. +<6o̠.@QH}N]B&}_6*cW/ +Soko56vԴ5eeU=#y bzl%$f F)2zYB콲@۬J`ޡҿ_[U@~DZ5@Q(@6NPr)!M\N ~gE/QRl [DT,B'Zk+ZF^G۲ro*$Y؃wB}!gpPp~P^O@.?ifWF#>YrCx`/X L1E%z 3G +BN ~3O@eTԯ{C| HYhttd{=c휣;.= ?mm7@ `Qkʞ>}zժUW_}D>wɉ /A^rp<>%)rȨi-j-/u&pGg:V^"uYAWmخpicN|*e엍,2E +]}j5GSu.Yy8Vނn[W |ڪ8>m[ȳ|N/=9.Yzxڥؤ0-bXUyASpE&y嶜_djQMG V3o@?˪q ;#|K¢wWqU5dGXhW׳lJvWsSS1r=ߙoZ +ij6r`B,0ڳWmAbINf 'HDgWa: +pUVAi +rPXx\X{O{ .__N8F +R5M*-ƍuZIM6IB_A':]PM}Tig#/iebbsZm&mj*mZ ƀFǚ?ܯ@tFON0́v|q73y=s$\3OIS{wzu<﹖ܴ̺j̡TZwG} +?ȹf-ª'||ERI1X޳PɻBP:,K[T7#W#S5GaxkFm_v}_dFgL!<\EƭBƭ\ҥTԾP:Ym@?F⹶Q,&w:w9y}teh-0 +MKg++I}^އ&ϰڵ,%߃'ewUqĒ +WK=H,kc?\O-Q' = SC Ͻ.Hm A NR@` +ے@gbb;-$]-_q]0[L=f~|  "H v~b/j` f&bZ(Cʎʸ҈#^ (f +N&'tI>MD:u5W6ҭ۷3c~D̩ _b\hikggP_tMUhIIKjjUBBYttQhh^``O{S]Euaur}EKYG7PǺEYj:XXjk{?yO[.˵i򿿚B&?&уҖ"oG1]vڵs;v9O{QWW 1167*tﴑs9|lcA@;::.63|>o΋K3WN.X{֟3|a/Uud>c?rJ+ +.WAr""km + V޲_ċs6n7$Bqt80."c=Btrsgñk|O'-%Tҹ\γ^γIƞzcgWAWQmqE)"S2'„V +#cc.Xj|:(+c'/ٻ7mRRnV޾bEʒ% ͟~Μ5x>~0kwiuOX6j}7  7/vΜT_W\m)Ҭ'Nץ yĺO +'į]vʕ+#""9R{nyyp: gV\,ڮ &|1WpC<},}wSj}%33kW P \:-ڶ;ݜ̭Yt1X) %[ź;N³tEG<˳_F煷{|2w-6=qۛ}w]=O|<#p\;Ł5fb׿J{cutR_r[(-g>=h㳼u찡sB?;t!Nd.7;YtQz~p[ϵ p#NIZsRu\0is==x?O?3~όZu=b,m};k+te:ͼ1%ho߾ů&:q[K iӦ9 li\0++kUh@n/Gdgu]|jѽ{O={Խwޙ_.]v|ѣ7v L0K8i~#? t\ Rߞ9P>o#F3/x/]/.>gCr.Y!l&Z.MV$X?kPyVG6s:|ϟff۞jəvUTTn޼yڵbmϞ=+'?v+9d ڳߛ>,uQuٳKycA /Og d;VώgӅ玟O+*8!ܾr9JIҜWsmNbhwdJo +?vy_gϞ"/ʽ(Vϋ,|m9!vSZǭ޸!zU׭]|ek]fnx'U)'x{穸{xylݓ"8Ykhfl/ԥK~?>~'K/mٲE2d]# l_{禢 G}1ydœEgŭC_o'G`tsV`=ᛇ=_.]*A1 + + V;wJ}_RtŢ +_/jN;~ W6,R%dܸ^Rv]Pxv +G1<|aOPzNҤ}>K 41kٳ'nݸrB'$Ĝ>>.0c1bc1*JUN0cc1p 1c +뛓+75i_EbP/ƺݠB~`h˙ cK\wUznyZJ*~!"B /OHI.`h.[ c1v `q/j|u+"6oWu\^Щ}^'B ,HH2.x2utO\g4Q3B|7ѕ3("Wmukw0.p +\(1=̚WR*E_jRa_@ 1` !z.Xx҆#9MƝw*S6gp!g2pB >tmby9)pSf͡1[$rxkq*]08.. +7lyUWUy,nNvS>_Iu-Ǟ6ppq +B3 +]sgmol_^Cg $`Ji{yOp'Mp%84qՉcWs&` +cSӛ49MoFn)us jk\%veYsR@&??^]on_>3&>T^S h0 DxWK`L11ۼ,` +D.,1M_4.0܆v1rU\`g\ +g͍ \=qFn6 ۸ #xpv?;,rA{NSw>t_嵇=W#Zq`mP3=7(G>1 ^6X_@ +@!d/II|7}Vuzڅ//XU o1R c0-ʘ ^:I^Iǵ%hta }kpO<7gsܤYRCR9893(Q +g@h@, +Q<;Ks.>sw٩ȭ6&:t谞RSS9pR4ऌ-9;.u~¨=EKwאiL +lZشNz>p\ z +`B o*[Y}nw**kO^9CF{lJ\ r\=i..hlrq<\շ 6^t>KS64yl \.of ʣ|#9ߕn8΅EƈU ,8Qz.0(YA5Ԁ's• *_wjWekW]O؟g3O6{g>]QcVeN~k5lV_-<ңÖ|?lsSX#*_`9udj.[S!$.hll5sJwTT?ZSi^'L0x:H ]|YɁ N?/΍wn|`û"tM|{w#}!šo^3,8;dA <6./78ui\Uw@Psf&|:1ǘL^Ă-&8ɴE|tUUձc$^2k2-\ Ĺ)q}vG?'xMXk^{ߛ뭀mAsFk_[m -PX::@ ZzubbbVMyYڴӡط3oܸ!(Vlr\"ZCn٥U| l=dG?gLzߔ4OswkI/ys/ . Ԅ~lS#">l67O]:~c_9<1wQW.Wm,.0wگE+ǘg> xyDXQYh/w u|^ +p Ęe,`n~1 +0kBI\ j/z +OJsA"h.C +J 0-w͕eo\Um`apWY9G38p @`&B[|$I.T|zȼas㯄l:s¿OfggUUUq.> +Zڒ& +g} \|O]wd#6ptF\|zঌYN +n;&% 9%;!.LdM '29AmQfxk_̈;&8 !}挹IddMʜx:hٛNnJߺƣC8\ )U+o^'N9rA}>+Rʏ.)kh! $^1n:>'Dp[ö.ܞz|~"_ff9i7t< !| LD4vr DVY3M\PtnaB/zu̒. +Bp;Lji#|\%3<5'b;EځcW{LX3()=lq'+. : +6S i_/q>]pU~I͍N8rgop PTc ˭ lk9KvG*iD~]biS2 p\ ++u:0r!r `-7*sA3Kw[}]'D|{Gx]ԭnv-(k߹{{}#k|#Jyk̥0p 0 +lq +t\"y5]UV:pAjZyQKG.olj&Ot+477/Hmn?CѧlI._SJ6\ EQ% Ne:y$āSH;xt\yF;_2A݇wпc=_kݷ;~ya_y"s :oم 0&eģ,?G"r,Psmr4kskOR˽9uޱU7*7oj*NvĆӮ&`6ԷpA# +W\Y'ٗxhƃ;7>c֎Gd :&5s~{ی=fڠၟRx?&?&s7p9#M =d}c 8 J.N;u47H։w,2g +{jWߛѡow{Mo%€)4;K/.g!p.'Ă <087F.@ 0) 8 N?/΍wn|`û"tM|k斟_?{hŁwA?G{M /,f\y,V?O_̽S^'0ੂ  0t.x'I]ƽ>:B񛇧wx׷}/{w=gk4..빐9t71y\`0xAh Yl{ I ,~ऌ-9;.y.rGvG{߽7}ŎcšAIa%y 0\9p#0S4d+]w:0g + 8 e{/M5Z ܘ ¶f.ڞdg~Ԯ^0jFt蝾%~+ۜ6?%CJ +"RGB[ ״tb!={`L~I[n.6c;,ݝbo~v7%}wQ ZحAs z*,2.@pv`$ED0;UYׁxV9@ 8 pE.0N G +5";½Yd#}4Xԭp9kF +XewG5H|=?j. mbhǍh`Xh\b,4 GqAmmuTݮ,+tN +d8Mo?w30k@G^hц<mH2s?7 +īD^3D) FdO)COC~r6`qۆfg@n g<#\= J.N;u47H։w,~^ :Hy'uȠC;}߱G@ft^{ 0`<;ēy80 #躀}`pC*HȰX `ႬKB{[ aq.x'I]ƽ>:B񛇧wx׷}/{w=g:OXZ3})/un?Cۍl岐\`T@UCjjr`w]\   l IwSgu]&4.JG뇞'}az)#.0,p%\Lh`n +cx*C$ +ZVz'* .p \@b$PS8Lg->W[ lZ=d3^6h +NӬ!gx +O0$ ə ttB )`?m۾=JhK,\ +Z\GdepMX{1f.0wv*G{jqn>?&6amRyD2  BF>SV9|-64bL8u$Nmtu=J|lp,HG +Z,#[T>, +J<ŰU\C-yq Y[G$1qۻ`9:L_.;ST͖6arqVf@W-F@0XđUAG@\CBrBró9t=};Ow|ЧOw_}?[̎u;x/ +, xR5E} ! + r١j +ՠ x +{ +Olڣ)A!A崚̫A* BNr# +paF P +({e;!?9)z +& @b"U +v:Nr#*\.)/ +,jTZF}ej I+@{ 9)'eaWZaAr/?^ +(~\Wv%E[oļ njPv`|OF4Qq|QY{vw R@ߏdqM:GYBsz0vhfe%B)UAI B;!)Y4f7|_Cz\ / +t-Z{AyIe]ɑÊ x0Q^3(HAK + +|~4Q(TeT*Hq}&/U;A߽>|æ5{мdСC1=z鴽[oݲn;~;vlWx__"};` {G8"uK>SK/d6E/ ݽ /7l_2|-/r8ZO3\L;a?|35[_՛~ΗҴ/otzGqbJx/'TDDdB ru5mr/XAO}4} K }^0qDi{?9}iNN6=>xFڱck;S[6t_Y'TggM +DDdbLŭRknYP&,yefZPT +\SX&i*kش柗C){߲m'z=3>[ۏ^H}Ԗ o{Z4-MlGohT6R&$c:m/޶ן8{{3I/~Y ŭG%?P_Izs1.:nѲc]v̢-c/>㎻`ѱ.L=vѲc[v\'l6|&}E,[w?{7䟟3Wʼ߼mK.^gSxʻ4酹qSn0/ֺplNkM_rrzM'rI_:o<N|'pN'nx˛o|8777,>G]zq(""""Mȿo{{ms|s噻M ӒϾˮ[{]++Vwݵkvu+׬fݷ5+[W^^{}m嚫W_^q^be+V^lՕWZq+.{ŕ\qꫮW]}澫VܳbŪ嗯{.Or]WYu+һ% \~OOy?LsAxy?<{;~|{i>g}]zGޱtK~i[[n~ǩ7zWN:K'p rYl&?;R/HwϤwvgɎ#|S>|Ob+״aʕkfMzϮ]yڕWi啫V\՗]w]|_>[yw]2{͝˗߽|=w/_ w]r/ky{˗?j/6O_yA~Y?8=;>~g}3|3~goooon{o{|o}˻y˻NMo?7?}s^ΆǦ~˙>1I[wO?\x.<#\}Կ|/G+oQkQI:WCGGL~{_ۦ_Wm{6_Jnjg|q_ZLo󣩎k:\(SE9w7y7s +oj&M :endstream +endobj +123 0 obj << +/Type /XObject +/Subtype /Image +/Width 1032 +/Height 746 +/BitsPerComponent 8 +/ColorSpace /DeviceRGB +/Length 48236 +/Filter /FlateDecode +>> +stream +x\c}ַgYWuuX`CDQQҤ(RJHHޑ{!) +¸1KBl{?O>7s枙$7TPATPATPATPATPATPATPATPATPATPATPATPAUwcOe^|J6_u+k ;_׆Kbu@ cj,Nc4JP#EN9\ 0@̕Q +̋y7@ /TrXę3Ba`Yu&.&͹N +=aq)[.XF9W 5#G + NU[Ф?GG|Z|TO[_No{2#qZp5n^>/.N=N ю*ū+9v6ѣm,:"!ܚ`] +|Y^4W8gЁ&m%Vmf79-4Q +&: + +mP>9NfkZ~8GYaDx%T@4j*9ތ]lMtH]/}1vZzjE 粖4ZY~leiF~x.Q;܊;JgގƯ9 +#tѢvS3mG=~ُrw#`4IhDriLơLfS$R m%!&"I^rT\|` )DFhCi F@e# 7tv3ۼ㥸hO7x5ڶNvbIۉ'W]i±.|bN>JfW!V"oC^'O^ue4y$y8q +]`I?i +,kKx;!h rvdi)hL_Wv}ͰVihmtЧ}ǯ65Fh\@T'ap;';l/&ev­M 冷1w`:/Lha|[8[ftM?ȯ0fpҙ# @ #ĝ +kTU෷L}V"R"P&7R0;OxMgl)9& +_g_.N_a9%ڭWm?ܺ ䷹4#k^ Nv i\'wpi}X +~n|1IoT&[m2kL-dKƚs 7&v­^n2^l>vO30f3v/>ɌjeFgtxװbMܴJ+K4|+m_]j~>7~fܡҥ'`F0]d$\xpy!BU-XQ|xyɡ%~.gb=_U|sRw/.;+M2 +sDF^ Q >%ç ]#Ҡ6R BjO bϳczt3"X]93pg&v +d~ßVJЍ!!gb=u^Mu,**LV׷q{TXoTZMpJ06*`ɉAoWZMt5 uZ< +Xe&)".,6$ZM0䦣0˺ǹoEz>旇,o|xlxy#Rۚ{j%).~ꂷk-L S<Ҡ4!06./! 4,/ '> '=9!:G򦕾kI/+)O,쇄1<509Ӡב}~îC F<5,5r#jX.a^3\&$cjJcN1Ml OzȠa _WsK}B8JZKՕۅE +9.nu Ybs7nHIKy򒎮 }+&e`l:g6Ls^ .\z|E?0Kn[,C_ $jx𞕡ZO>gv8:܋p:,y߼ 6NGE,<􁿷Srb(4.ɩLI +똉^N + +w6^$LU+lLefsݝa-.j;?!ׅL`6YGGS7NVg\,b8>0#X^SIo Ii/{kdfh«^Pd祋gㅞB瀿u[k ]T߯|H#faeMԶ^P dI"ý3"!gl T`yeY: +>JGc1I"mp nd{U mc.Vׅ[t?x9L" R^<{֧?=g&OsxC;N%ޙ/$u<$e_Ͳ {#xLr CLJ+,",-jr1deeI>돎DxO,T3R w +4x纀 +6]g|wDPYu~͛7mmmX3;7Lyz8FT%՜x(g~ )ϞSQ'’5YyղwtCPJ^iCAYw HI)-iyq1BXb6 L =OrLJYayh_-VSf)i~]|> &DAP ŧOqʢ܋c08DɯtOk3y4\ݍkjj ==S#-on~&`F}ow6J:ֈ :kkr:""_ j®R'<~#@8.Hl{aO(3cl밖JrGPA )marBZNSBZ{L\e150c>+鼦ɐQsFxG%X\k"خC#N}:0xnrczY>wrtn695"U^4xpB頲 \[3s^n(bC[幟 XOVUU~ɇ_?|ô +526 οVlQnUiSk[oנ]XWW6KA=$4sxyF{CIh+^KEE6^ۡ[~^Ř|߬X_J4 vA}4y0\I^Vakmw){[c +*,d~c;|8i6qI:\o:&ϖN6&|Ę 0} maFԻıͯ$$L~1Zr=!5[$X>2vŖN'4$X ۥѓO(Pp6τx ^ CXBOHt!!'ś\{򪌏-ӺgD[`aWaaoc󋱉@Q~lZ6)>QPK nU⮫,#+# +jʫ[*[J+Q%ešqy>B2]}=M"ʪ +J[r_k;&eUE%m? f?>=LJԏ+ZKYpi!2VpܥNE5>3:i-ԨjP9ի]Lk0SG]|yC]~ZZҳgψ @ 477z xo'Lto|/`8rz4`.+.K#I{ *zxr/-d;޲g#χ^^t?/\p ڰJ 8f| +) {iVgr Yu`qSN!2e]"|pduYü.M,S[~e[ Sڰ;ܰNv)ṡ4~apbuGNYʙsá!Cw5GzIg^=K/%I|і4NUҹcbofMXw˥+^2XT +rug1и#1eV<M +"jkRs(zт'G 4I} lb9'4%x$?Hp'~~~{ +]c5R %&~ b0hkkl]P]*jZ#ʂb"rj[4ltx/`ꠒ'a%`}íXȩi"c\=}(j{1dO +}r̦OWp:NF\$uu W_#'Ok7߬W nkZVՙ,-rlr IT`~$Gv/SVSUxP ɡ].EYڑL+ΏD]ݺ, @mkYM )xh/WpinXsMVֲD˸D۴BOA֚i鬒i5F/Ղg_7dWG I٦+z)>(s/1_8bǹD /XvtH#lL[8L{1Sџl~y|Rm\ +{xto(&CϹtp|MmkG6OW,rKjJN{" +uX~jUWe[nlf3c^$ }+`SiEzW2>䙐mBڙ +IUÜ.x ^h@gt6{ϲ iH٤i1|/nfKCЄ@M1A}MA4>·'Cft'#~ +;3fӦXgoWLߤtѰ_ + +yrԺ3c ,U NIË+_YӕW7w\Mq~ŋ8Lj1׷r Y5l);4뇴5xv+s̿jq$NӺ9%\&~E[o`ԦgЬ}隹Yz&k\4W6W^vN^]K +o+Q5 +//lwCh7鬙#X691b #nQ*/'؅Z׮)U,;blBA@^#BlqGrջjG50/L`Ҥr#g쟮N*dӚAP(#==Cܢ\O;lQ󩱈cz׻'7&9=b"cǼ y3XF/r',/%y]۰Xau@(& +OigUrz{}`;<%4 +~qd3B'g-..[=j^v铥2劲'E#!\d0 ^邃1LRa0zU3rkC6g=wA``1/xSda9Yz矐c@" +9'Oξ +hO:~:`Ҁk +UU%%k231Q}N@g~L߾OxvDb1'}|cճ'ZjzИܠ#JvYsqZ JmRKSKBTCz}[e~ wc +ߖx>3UtTHOODH3,C}g99*&!ʽI;=/dm8 +V--c.EAeٴ>{HJFwR*ɃTͨiKVÚ;W^m\Uhv'/,j*,4 #@蜤G +Oߐ^m:?,t +ǟgS{Ɋ}w,߫d-y=sF3I$S>WʹXֽ\A[7 +l.1 +gomx/!Y_.sK;L3ێ +(ep1y\ly WNS{.!\gdL=Sc ;U|n'wVv&固o:} L/6,Uօщ%OjzZ[ᵥoX tC @y@=g]u۴fND1 hLq3 +ƢZ埾ގt] +7Dwׁ  ,!8uh Tn? 3~Ak ]KE1;/Sl1J +uUY P f.bR6VVVU + k*Kcc sCB2R==]\bU\ʒgo_Q'7 ++:r-N^AݡU9$s&^;jeoo*/90w>ߠV/ߐ^ʝo/Pndog^^^ު*")__ô.8q 4*RU$I/sH;g +\75Hާsm ˦ӯzՑkD̙1@ZzsC!hQXNt0/ɉP'7ZtE콼*V肜d`DŧRE.'?kX d2QUl.u h`h"u2:Ds/.nLL,+ JvwOpr}v^yOSKtQQ؏-N*|8ZPooali`yϮZP܁(5Q9jh%-:h1sӶCPR%H&piSpK癟2Z,:Ivv}Ym|\eWڤUs/^>JT OxoLÃu,cpe"XoxC^.93n]@ 0qOE\9qA׭ǵoܼ7}R:Q\.Ҟf%` 銠MUˌK橐N0׷6=$<{w}>ACWo|vv5?=[wvSDS4U?Z/kX}Uc{؀nfIu]P2Kq{4Ƥ+**_~ -^ik^RFSIy’.Ru Y,qugY;~8Oph`q,7=I^O `O酜 L5\-wd"  qXW>hyY ׊ [f~{:4"x߹~IYiۄ>zD|?Li΃ -Yuʹ +hf*򼯏5Y]VRҔ_QT[cfBC|Fƍ7lذ~&)yfP111^f9ζ*g&^=8[랶KsrZL3z-9ֲDx3qOEn KBJ6f0f컕Ar  I +^Kw?y >?۱ М9?m>Kv=s a +=r$[B9EEU}9_$|+c%v񼅟ϙ3a"-|^~3|U3lO` ڋ^kyI;رԅn(d@rJ?8sT;CV޲쿞cmEyu]u"|9Tdı52^gkrr z= 2 Y0( s_rl%ē:n>xO~Nk]Чߥ)eC[=|LjtPOf-gܖt*%)ajҨ߸:{lQ}/t_SO>8̳=炋.ѳW~]*^P_ F _ "_P/{/ED/^ACuMm׻b~x'H;?:·J%|5E;=:zxI=iQI2z?_ljHD@DDDDDx""""" DDDDD/^ """""zA+d̟=9U0v""""]w KGs%2%M^m_DDDD(޾Sw Ԁ 6HEFʉ7z=ፍo񯇵%y ih=\7~DoOec2ظq}{lĦ 7_iCpfm[.j;3q؛ǿ8vƿ̷_޿lzkMgnvj65yzۛS~Ά^ټa'R1Φl޴o'zzw6o~;Gid6fs׶7޼t̻߾;iKwI^-%8-9 sߒhK-OMvknI?ڞǴөlٺs̶m5mn^K~̭IUvz_?2o?Z'˞և|=^_@/4."@Dx/^ "^ %_ DDx/""^P^vݴ[<q/zԚoEx/ԯ^=򏾔/FD4D _sH^i +J=y +rߒsn=o}O>_ؽ釜zv).GH?}K#js~A_' +>7,dKf-;sbG.h,w?hg x +rߒsn.a|rpON~C wuyOA*yA~A x =o8uϝSV޻Ѳӿcf~b +^ + +{z^=M7 xx6rAIA9Ȝ5/^А +xfHG^`~/ +YOd # +5*R +-xI m1/@cN "F^P^pkzN53 7o_;uɿϜt'r%m#, + `|.E0'ؘ fW;b©^x) +/ +** !byL/h id.uXCfyA] N91檁C4rQ0rܔEU< P灊 ^~Ifna%y +F < R]3Uig~_?/5 (~A^q +2K ͂33o^>/Kx +D +킽|!// +ڴvFֽ*vwc@Tu~^2s%Ѐ9]1x#4Hpb܇Y\kƃU;fGe)?r`̕YdмC<2lo\=b񚑋 [jLJ.x+_qܶ~(w'@\` "N kJѕsi4r؁~A!F/O~+.y Ye +-ٗHA3G +xA^{Kϯ}ɕqD2Ko>|WKte|#t5 +x Yi[ƘYPRPM@x/ +vCM *SrzA BVg +,j^ J;(Y*5 4 P&/w߮2٩P- +YY*%ݬ R}n_53)w: xxAe)M?m۴o[-b_fA(|{ Y:Ÿ>J8!;򕂼(^ 喂+/ +rv +rAK mS^*y/ +kf!~_BkLSEҼ +{+)(ir/ +WVߛO*͂ҪAAށ%L> +5]ɲxAV/HVzijG b^ +Rՠ" EPa[ f^ xPuBAMk'Iڔ";D;4>h4gњZv TɺUYϺk5 +kP4D Y-T&zidVtaYD7T+ +_X4+k>O^ +,\#^ +e +x/ +Ji4qfִ T DDx/`^ xA3{H/" x/@Dx^ x/^ "^ D/" x/@D^ """R\9moW= @@DDDb `ܹkz՗ڵk^䷫OIX^~W_^Hkx/^ """""@DDDDDx""""" DDDDD/^ """""@DDDDD?s𻇟_<3| 9/*yHeCܱ&_}-m{^3=߯N˫ƴ{T=/c 0wE ]^ck!8O˜e^_-t/jA>/qyoރ8ߺn;v;κ;֮SZۊOR+aʪUy>VYB$^K1\B`dNNι$}sܼͭv + l5 [{GL.0 ^Jц\ +&R9d6N_U&zYrcDjtM2 +.,̞& p~E㿻.0Oߓ;!URƼTfo54K +r +F߾m]d0mEgM-r +m;c}D +Lx/߹ З:}ieoy M 1̦1;4іWIF ;K/{{'z$ +|v +cG > >Winڂ+A0'n[U9k +: +B `vPeEbn0ZoU BCZ;a>YfTYG 43?1 Cfk|?%~y-Z}7??gyԹ>ߛ%᳧ +endobj +124 0 obj << +/Type /XObject +/Subtype /Image +/Width 1032 +/Height 746 +/BitsPerComponent 8 +/ColorSpace /DeviceRGB +/Length 53238 +/Filter /FlateDecode +>> +stream +x\c}ַgYWuuX`CDQQҤ(RJHHޑ{!) +¸1KBl{?O>7s枙$7TPATPATPATPATPATPATPATPATPATPATPATPAUwcOe^|J6_u+k ;_׆Kbu@ cj,Nc4JP#EN9\ 0@̕Q +̋y7@ /TrXę3Ba`Yu&.&͹N +=aq)[.XF9W 5#G + NU[Ф?GG|Z|TO[_No{2#qZp5n^>/.N=N ю*ū+9v6ѣm,:"!ܚ`] +|Y^4W8gЁ&m%Vmf79-4Q +&: + +mP>9NfkZ~8GYaDx%T@4j*9ތ]lMtH]/}1vZzjE 粖4ZY~leiF~x.Q;܊;JgގƯ9 +#tѢvS3mG=~ُrw#`4IhDriLơLfS$R m%!&"I^rT\|` )DFhCi F@e# 7tv3ۼ㥸hO7x5ڶNvbIۉ'W]i±.|bN>JfW!V"oC^'O^ue4y$y8q +]`I?i +,kKx;!h rvdi)hL_Wv}ͰVihmtЧ}ǯ65Fh\@T'ap;';l/&ev­M 冷1w`:/Lha|[8[ftM?ȯ0fpҙ# @ #ĝ +kTU෷L}V"R"P&7R0;OxMgl)9& +_g_.N_a9%ڭWm?ܺ ䷹4#k^ Nv i\'wpi}X +~n|1IoT&[m2kL-dKƚs 7&v­^n2^l>vO30f3v/>ɌjeFgtxװbMܴJ+K4|+m_]j~>7~fܡҥ'`F0]d$\xpy!BU-XQ|xyɡ%~.gb=_U|sRw/.;+M2 +sDF^ Q >%ç ]#Ҡ6R BjO bϳczt3"X]93pg&v +d~ßVJЍ!!gb=u^Mu,**LV׷q{TXoTZMpJ06*`ɉAoWZMt5 uZ< +Xe&)".,6$ZM0䦣0˺ǹoEz>旇,o|xlxy#Rۚ{j%).~ꂷk-L S<Ҡ4!06./! 4,/ '> '=9!:G򦕾kI/+)O,쇄1<509Ӡב}~îC F<5,5r#jX.a^3\&$cjJcN1Ml OzȠa _WsK}B8JZKՕۅE +9.nu Ybs7nHIKy򒎮 }+&e`l:g6Ls^ .\z|E?0Kn[,C_ $jx𞕡ZO>gv8:܋p:,y߼ 6NGE,<􁿷Srb(4.ɩLI +똉^N + +w6^$LU+lLefsݝa-.j;?!ׅL`6YGGS7NVg\,b8>0#X^SIo Ii/{kdfh«^Pd祋gㅞB瀿u[k ]T߯|H#faeMԶ^P dI"ý3"!gl T`yeY: +>JGc1I"mp nd{U mc.Vׅ[t?x9L" R^<{֧?=g&OsxC;N%ޙ/$u<$e_Ͳ {#xLr CLJ+,",-jr1deeI>돎DxO,T3R w +4x纀 +6]g|wDPYu~͛7mmmX3;7Lyz8FT%՜x(g~ )ϞSQ'’5YyղwtCPJ^iCAYw HI)-iyq1BXb6 L =OrLJYayh_-VSf)i~]|> &DAP ŧOqʢ܋c08DɯtOk3y4\ݍkjj ==S#-on~&`F}ow6J:ֈ :kkr:""_ j®R'<~#@8.Hl{aO(3cl밖JrGPA )marBZNSBZ{L\e150c>+鼦ɐQsFxG%X\k"خC#N}:0xnrczY>wrtn695"U^4xpB頲 \[3s^n(bC[幟 XOVUU~ɇ_?|ô +526 οVlQnUiSk[oנ]XWW6KA=$4sxyF{CIh+^KEE6^ۡ[~^Ř|߬X_J4 vA}4y0\I^Vakmw){[c +*,d~c;|8i6qI:\o:&ϖN6&|Ę 0} maFԻıͯ$$L~1Zr=!5[$X>2vŖN'4$X ۥѓO(Pp6τx ^ CXBOHt!!'ś\{򪌏-ӺgD[`aWaaoc󋱉@Q~lZ6)>QPK nU⮫,#+# +jʫ[*[J+Q%ešqy>B2]}=M"ʪ +J[r_k;&eUE%m? f?>=LJԏ+ZKYpi!2VpܥNE5>3:i-ԨjP9ի]Lk0SG]|yC]~ZZҳgψ @ 477z xo'Lto|/`8rz4`.+.K#I{ *zxr/-d;޲g#χ^^t?/\p ڰJ 8f| +) {iVgr Yu`qSN!2e]"|pduYü.M,S[~e[ Sڰ;ܰNv)ṡ4~apbuGNYʙsá!Cw5GzIg^=K/%I|і4NUҹcbofMXw˥+^2XT +rug1и#1eV<M +"jkRs(zт'G 4I} lb9'4%x$?Hp'~~~{ +]c5R %&~ b0hkkl]P]*jZ#ʂb"rj[4ltx/`ꠒ'a%`}íXȩi"c\=}(j{1dO +}r̦OWp:NF\$uu W_#'Ok7߬W nkZVՙ,-rlr IT`~$Gv/SVSUxP ɡ].EYڑL+ΏD]ݺ, @mkYM )xh/WpinXsMVֲD˸D۴BOA֚i鬒i5F/Ղg_7dWG I٦+z)>(s/1_8bǹD /XvtH#lL[8L{1Sџl~y|Rm\ +{xto(&CϹtp|MmkG6OW,rKjJN{" +uX~jUWe[nlf3c^$ }+`SiEzW2>䙐mBڙ +IUÜ.x ^h@gt6{ϲ iH٤i1|/nfKCЄ@M1A}MA4>·'Cft'#~ +;3fӦXgoWLߤtѰ_ + +yrԺ3c ,U NIË+_YӕW7w\Mq~ŋ8Lj1׷r Y5l);4뇴5xv+s̿jq$NӺ9%\&~E[o`ԦgЬ}隹Yz&k\4W6W^vN^]K +o+Q5 +//lwCh7鬙#X691b #nQ*/'؅Z׮)U,;blBA@^#BlqGrջjG50/L`Ҥr#g쟮N*dӚAP(#==Cܢ\O;lQ󩱈cz׻'7&9=b"cǼ y3XF/r',/%y]۰Xau@(& +OigUrz{}`;<%4 +~qd3B'g-..[=j^v铥2劲'E#!\d0 ^邃1LRa0zU3rkC6g=wA``1/xSda9Yz矐c@" +9'Oξ +hO:~:`Ҁk +UU%%k231Q}N@g~L߾OxvDb1'}|cճ'ZjzИܠ#JvYsqZ JmRKSKBTCz}[e~ wc +ߖx>3UtTHOODH3,C}g99*&!ʽI;=/dm8 +V--c.EAeٴ>{HJFwR*ɃTͨiKVÚ;W^m\Uhv'/,j*,4 #@蜤G +Oߐ^m:?,t +ǟgS{Ɋ}w,߫d-y=sF3I$S>WʹXֽ\A[7 +l.1 +gomx/!Y_.sK;L3ێ +(ep1y\ly WNS{.!\gdL=Sc ;U|n'wVv&固o:} L/6,Uօщ%OjzZ[ᵥoX tC @y@=g]u۴fND1 hLq3 +ƢZ埾ގt] +7Dwׁ  ,!8uh Tn? 3~Ak ]KE1;/Sl1J +uUY P f.bR6VVVU + k*Kcc sCB2R==]\bU\ʒgo_Q'7 ++:r-N^AݡU9$s&^;jeoo*/90w>ߠV/ߐ^ʝo/Pndog^^^ު*")__ô.8q 4*RU$I/sH;g +\75Hާsm ˦ӯzՑkD̙1@ZzsC!hQXNt0/ɉP'7ZtE콼*V肜d`DŧRE.'?kX d2QUl.u h`h"u2:Ds/.nLL,+ JvwOpr}v^yOSKtQQ؏-N*|8ZPooali`yϮZP܁(5Q9jh%-:h1sӶCPR%H&piSpK癟2Z,:Ivv}Ym|\eWڤUs/^>JT OxoLÃu,cpe"XoxC^.93n]@ 0qOE\9qA׭ǵoܼ7}R:Q\.Ҟf%` 銠MUˌK橐N0׷6=$<{w}>ACWo|vv5?=[wvSDS4U?Z/kX}Uc{؀nfIu]P2Kq{4Ƥ+**_~ -^ik^RFSIy’.Ru Y,qugY;~8Oph`q,7=I^O `O酜 L5\-wd"  qXW>hyY ׊ [f~{:4"x߹~IYiۄ>zD|?Li΃ -Yuʹ +hf*򼯏5Y]VRҔ_QT[cfBC|Fƍ7lذ~&)yfP111^f9ζ*g&^=8[랶KsrZL3z-9ֲDx3qOEn KBJ6f0f컕Ar  I +^Kw?y >?۱ М9?m>Kv=s a +=r$[B9EEU}9_$|+c%v񼅟ϙ3a"-|^~3|U3lO` ڋ^kyI;رԅn(d@rJ?8sT;CV޲쿞cmEyu]u"|9Tdı52^gkrr z= 2 Y0( s_r_?S?^Ue+ݒm{gεv5pغթIT?x8 +o̸x'xBQ^z}/N}s̃[̙`~o zֽ.}&nذkժ.ݒżykg\5m҉`ԨyCΊ|dobpɽLul?mٓn|9#G:tSO7p ^,de}zBB۷]&}Μ9tRIlƌS$~G;R\)ҟf#<˃YDĮ.Teoϒ9u3$)~VΝ>(d^V,~GVr%oZOͪ|> T)={-|crɿ_}Vײc6.ܰco5BhF= *vp?/6oz*wUklnWYG2LjE.1hz*}>=t׌OzŶ۲,,~=߹t[=xY;Xxo/պS^LO?Կ/UKOn/uj1{,{jE{L-elaX /qƟ=kGi7t2ɓ)fп?H1S.Q{J4߰a ]Nڵ{NZF;Wڱr7YO]PzJ;5n|ul?,LeԻN{;_t.>y|Mw\euVϙwG޴aIf%ݺuy{9'NسgOBؤ鉫W.8vd`cw[I)cG<>p gyZ=KHU/[5u]cF+n3bT|?Čs۴l;/w#Ɣ<ѐqb>hպ|/+Wro;^k*VܭG|,&5aeR>+Y^ێC |2iVԱdž1(S}Y۶;vԵK^=z?02f5|i{̮?8@?oH7߿^ů+-o׮"Ȏ-DG Ӻ+¹jpk7vG³xM^~\r;sGv޽w3{ o66atj O3eW>Om|My۝mo0YBx!B9LjCN/SO}خoJ/ͼpwǥXv/xG:.qXFĶRY]G=(cVO@#T߭x,݊ IOmY fOIJ7csfO=kʜYSΙ5I-4:6f VWjya~R<;:%#؟ɷ m@<;av+^v"_ QFG~?O_W//[W ҭC֤{r : By |޿3_g'!ut "< |yVA/ >?A+A 4lwrifxA 8p#`:~?oo.\6J +.]%mP, G 8p# +wwvҸN_?ܰv^AK +]Fai8-)*(!J**OYXXT9ؾ.,a̼sn_M= +ێ +k_i o&! +޵ۄ qדeMې=c㩙r1y9i͎>fĥ'.IZ;2m$g/ +ڮk  +L)^(%ulElJ I5-?]zp7%^ +_ _`l~vՈl׿BR=^ ۬[^CM&C + +Ft;5JzI^/٪-U_=Λ"j^p7uwhQזj46a,@O=m]6 |&/I^ +|]Qtz1V.u*ԉ(_;|a:=RKF iG +o]y7{KA./(o^ +NS%JP/k/8uxZTخk\)|h^Y^>~F>n{rH5K +ݜLr r\#2Ca +Wp lݑ9bԄ1S9fRɿ'5iGM7q &u! +G  +؍xB^x^ +*:A +y`>2AA"xAp \~U`}e]y8a k'>f#[ux8mԝҚ +Rrw@Pk+POdtVcZ$V j`Qi(Tv[0B +B9ߙhu=L + /0 Ν>u Ю;dlܻ:kAVzAcWHAO6T/R%]nT:kGX>LZ}#G~u6.2ic9 +(S L̽o\u-kRؼzޗRȥTXex_?JJt])"K'/}qQz`P&=<&(/h{_ǦE +ml?.4/MؑYr||x )Vª \T߂'*6 ಝJR &=$@mwy^c9‡ +/ +Aگ:/ T5Q92` '?ͤ]U/-&Z <3>?9 zֱi` +S B(H8gv+V^,wK Jk  \V (e +^ +/c/0  =^ +Zs[ƭ? Q/3^P, ( + +OwNγo?~Kk5j0tֆfhZɳV83@@ ӂ(צz\f.l;/);R޹yxjI5G;!o>jC4OFn56Eb{a%jP<-.~8_wat% +cl FM1ocÇ? 5`~i.A w߿}j`w#F؃=_jf} &vxc&PLu!^xaTJ@nTiҠCX BM +;v g~,/zێ:D ~}t__ltve;M;^Ie +b^'^ ś}*_X5ba[P[>4&Ϸ\TI +դj + @T|[SXṽe`W7֮b[QQtHHQc7oeޯ^[I 45?,^SO#"<)^uW^pwy "5\zYex^ +ܽ};/^kmz +z- B䗑:y|(ΑH=. Q+s){ ''?oނ#z;m|^ /d@tk"'kT _N^FcSz: ]/\~EEŒ5ȝܽ>_%Qnϗ +ž +()0 +ݹֿ~}v+Ko_Oͩ'/exCe:{ Z +^ +?XpqwrY0C/^`l^ +x^@ + K݂|I +nuxsWv]gJS#^ +/ + +;Z%Cj>^`DQ% +WjJ@e@zl؄AiX( +/  +u} +y^sO:7ӓ;7*xL@5#:x^ +> p{Fg-ʒlWJ{z?L^ +MjG[ӃX}Z^ + +0k&JOVOS,  +8!8j%/ +# &?*|`[h[l,5/5"^^@xfG|Xk!ͤO>&#|`/ nohc:ELxX3N?m bb= 0[؛^k vywfK/^ +^v/ +?.'Ӗ ,^ +}rUcz(⣺y e Pӑx^ + J~S/@e{%[  + +M{Sro +3 ,@k tc +2C"{^=ldO +3㞸Am +HIIH.`o6J PG|`p`>k5Üo|jxxλtt. +2b>ÇfsuA+#.( +l(j6``US!]A. +yN2\ t +qTJu0 Et +t +Cb&ff5.0. I] t.F$ t. Q. t.. t.. t.. t.. t.. t.d H$5k{uY[]@$Y5CywTw`. kwٺ$I?l QϖutY;O%Id5cq\ZϘ{ kG֌. Iq_S-tz-D7g;"mw}Xzy$IXNO͘D O+O$Z= JgW;cqtIdҋO|}^GKZ`wG{kGSa. IotAuVl۷3{#G7554rN ; #u.xS$ᵜEQOpYdfΏb ^/yVHÈ+6-WT|\+D.lۖߎ֖憆}$IV! +5ԏpd79}f(6= zħG6YWl FIw3O?.h;./tCXl٨ HtA/x ~QXODAiþV *b"GWl犂`Һ s2tGKg bcymڴiǎH5<nGw]@$yu|Q8L-kҺ º"vA&8ϵ$ӣ%wANSD_a̟PiQ QSBYcyȣ?=W$ c[^49Wlo1Du&ź߯$I]H=|A]Q<\?ye_=J cĖDdFA_ S-#io?[|'86lx aW_yF$o>3j/KK^k'2 "ƺe}M9?fSqu݇9ǎ AӥOU:.8 +o=z}˖֗^km=rw8uښi삷 Hd/("n~@t ׈GXX]Ķh8F%zvA抍s>> ~D*m@֙i\s;~p(w/䯣`bmoN$삈{YWj? c']Ķ&*|D?_A cSfFAX.J'圙.3a֞^J$I$I$su'/֔ԸSμvSL2 >&|SNYN 3;)WHԉs'%^j܉i':aO?y L8&]?i'_7y']ϓ煻&.4y bLExLܼ?S?m$IWX)su_]p~֯}}n0L yN -0̘3ag燉}frVrf~񗚙 Ϛ~lΘM35Θ~g>;iV9ίM[iwqӿxOyiN;S.>e_8iUo8L$ZoOuHK![Rl_vh< h9 xEڼ*ʍnټMVnZy[Vm\y˦ozo{ v-ްtEK&_d,Ztٺ%7,_ŠWxᦛ7._n _de/ , Ɵ,<7)Cw1ܮ]ts?s}6xmpW_ܫg髯|+ +^19s;Ϛv/8|+7ב$I/};tAk;\{ai>fG|*߶ &|zp:O}?zu_C]a+Wn^u0gMW޲y0b߰<ޗme/]aulX$ ݟqal06!6繅 .\.~]t7ܰv/ օCEo -ϒӱ.s<9]>9?9'.Ĝ,xُ.]ȅ<=8;{g(ƚO}ڗosrN`A?{ig/O[ר +Ú߅߅߅(]8Ͽ'gm+_h}sV_;atDi9W-'9>tM£x]\Z1QjLӘr7m"?ݘ5_ݐ9[}b`j ?9>rendstream +endobj +125 0 obj << +/Type /XObject +/Subtype /Image +/Width 1032 +/Height 746 +/BitsPerComponent 8 +/ColorSpace /DeviceRGB +/Length 48920 +/Filter /FlateDecode +>> +stream +x\c}ַgYWuuX`CDQQҤ(RJHHޑ{!) +¸1KBl{?O>7s枙$7TPATPATPATPATPATPATPATPATPATPATPATPAUwcOe^|J6_u+k ;_׆Kbu@ cj,Nc4JP#EN9\ 0@̕Q +̋y7@ /TrXę3Ba`Yu&.&͹N +=aq)[.XF9W 5#G + NU[Ф?GG|Z|TO[_No{2#qZp5n^>/.N=N ю*ū+9v6ѣm,:"!ܚ`] +|Y^4W8gЁ&m%Vmf79-4Q +&: + +mP>9NfkZ~8GYaDx%T@4j*9ތ]lMtH]/}1vZzjE 粖4ZY~leiF~x.Q;܊;JgގƯ9 +#tѢvS3mG=~ُrw#`4IhDriLơLfS$R m%!&"I^rT\|` )DFhCi F@e# 7tv3ۼ㥸hO7x5ڶNvbIۉ'W]i±.|bN>JfW!V"oC^'O^ue4y$y8q +]`I?i +,kKx;!h rvdi)hL_Wv}ͰVihmtЧ}ǯ65Fh\@T'ap;';l/&ev­M 冷1w`:/Lha|[8[ftM?ȯ0fpҙ# @ #ĝ +kTU෷L}V"R"P&7R0;OxMgl)9& +_g_.N_a9%ڭWm?ܺ ䷹4#k^ Nv i\'wpi}X +~n|1IoT&[m2kL-dKƚs 7&v­^n2^l>vO30f3v/>ɌjeFgtxװbMܴJ+K4|+m_]j~>7~fܡҥ'`F0]d$\xpy!BU-XQ|xyɡ%~.gb=_U|sRw/.;+M2 +sDF^ Q >%ç ]#Ҡ6R BjO bϳczt3"X]93pg&v +d~ßVJЍ!!gb=u^Mu,**LV׷q{TXoTZMpJ06*`ɉAoWZMt5 uZ< +Xe&)".,6$ZM0䦣0˺ǹoEz>旇,o|xlxy#Rۚ{j%).~ꂷk-L S<Ҡ4!06./! 4,/ '> '=9!:G򦕾kI/+)O,쇄1<509Ӡב}~îC F<5,5r#jX.a^3\&$cjJcN1Ml OzȠa _WsK}B8JZKՕۅE +9.nu Ybs7nHIKy򒎮 }+&e`l:g6Ls^ .\z|E?0Kn[,C_ $jx𞕡ZO>gv8:܋p:,y߼ 6NGE,<􁿷Srb(4.ɩLI +똉^N + +w6^$LU+lLefsݝa-.j;?!ׅL`6YGGS7NVg\,b8>0#X^SIo Ii/{kdfh«^Pd祋gㅞB瀿u[k ]T߯|H#faeMԶ^P dI"ý3"!gl T`yeY: +>JGc1I"mp nd{U mc.Vׅ[t?x9L" R^<{֧?=g&OsxC;N%ޙ/$u<$e_Ͳ {#xLr CLJ+,",-jr1deeI>돎DxO,T3R w +4x纀 +6]g|wDPYu~͛7mmmX3;7Lyz8FT%՜x(g~ )ϞSQ'’5YyղwtCPJ^iCAYw HI)-iyq1BXb6 L =OrLJYayh_-VSf)i~]|> &DAP ŧOqʢ܋c08DɯtOk3y4\ݍkjj ==S#-on~&`F}ow6J:ֈ :kkr:""_ j®R'<~#@8.Hl{aO(3cl밖JrGPA )marBZNSBZ{L\e150c>+鼦ɐQsFxG%X\k"خC#N}:0xnrczY>wrtn695"U^4xpB頲 \[3s^n(bC[幟 XOVUU~ɇ_?|ô +526 οVlQnUiSk[oנ]XWW6KA=$4sxyF{CIh+^KEE6^ۡ[~^Ř|߬X_J4 vA}4y0\I^Vakmw){[c +*,d~c;|8i6qI:\o:&ϖN6&|Ę 0} maFԻıͯ$$L~1Zr=!5[$X>2vŖN'4$X ۥѓO(Pp6τx ^ CXBOHt!!'ś\{򪌏-ӺgD[`aWaaoc󋱉@Q~lZ6)>QPK nU⮫,#+# +jʫ[*[J+Q%ešqy>B2]}=M"ʪ +J[r_k;&eUE%m? f?>=LJԏ+ZKYpi!2VpܥNE5>3:i-ԨjP9ի]Lk0SG]|yC]~ZZҳgψ @ 477z xo'Lto|/`8rz4`.+.K#I{ *zxr/-d;޲g#χ^^t?/\p ڰJ 8f| +) {iVgr Yu`qSN!2e]"|pduYü.M,S[~e[ Sڰ;ܰNv)ṡ4~apbuGNYʙsá!Cw5GzIg^=K/%I|і4NUҹcbofMXw˥+^2XT +rug1и#1eV<M +"jkRs(zт'G 4I} lb9'4%x$?Hp'~~~{ +]c5R %&~ b0hkkl]P]*jZ#ʂb"rj[4ltx/`ꠒ'a%`}íXȩi"c\=}(j{1dO +}r̦OWp:NF\$uu W_#'Ok7߬W nkZVՙ,-rlr IT`~$Gv/SVSUxP ɡ].EYڑL+ΏD]ݺ, @mkYM )xh/WpinXsMVֲD˸D۴BOA֚i鬒i5F/Ղg_7dWG I٦+z)>(s/1_8bǹD /XvtH#lL[8L{1Sџl~y|Rm\ +{xto(&CϹtp|MmkG6OW,rKjJN{" +uX~jUWe[nlf3c^$ }+`SiEzW2>䙐mBڙ +IUÜ.x ^h@gt6{ϲ iH٤i1|/nfKCЄ@M1A}MA4>·'Cft'#~ +;3fӦXgoWLߤtѰ_ + +yrԺ3c ,U NIË+_YӕW7w\Mq~ŋ8Lj1׷r Y5l);4뇴5xv+s̿jq$NӺ9%\&~E[o`ԦgЬ}隹Yz&k\4W6W^vN^]K +o+Q5 +//lwCh7鬙#X691b #nQ*/'؅Z׮)U,;blBA@^#BlqGrջjG50/L`Ҥr#g쟮N*dӚAP(#==Cܢ\O;lQ󩱈cz׻'7&9=b"cǼ y3XF/r',/%y]۰Xau@(& +OigUrz{}`;<%4 +~qd3B'g-..[=j^v铥2劲'E#!\d0 ^邃1LRa0zU3rkC6g=wA``1/xSda9Yz矐c@" +9'Oξ +hO:~:`Ҁk +UU%%k231Q}N@g~L߾OxvDb1'}|cճ'ZjzИܠ#JvYsqZ JmRKSKBTCz}[e~ wc +ߖx>3UtTHOODH3,C}g99*&!ʽI;=/dm8 +V--c.EAeٴ>{HJFwR*ɃTͨiKVÚ;W^m\Uhv'/,j*,4 #@蜤G +Oߐ^m:?,t +ǟgS{Ɋ}w,߫d-y=sF3I$S>WʹXֽ\A[7 +l.1 +gomx/!Y_.sK;L3ێ +(ep1y\ly WNS{.!\gdL=Sc ;U|n'wVv&固o:} L/6,Uօщ%OjzZ[ᵥoX tC @y@=g]u۴fND1 hLq3 +ƢZ埾ގt] +7Dwׁ  ,!8uh Tn? 3~Ak ]KE1;/Sl1J +uUY P f.bR6VVVU + k*Kcc sCB2R==]\bU\ʒgo_Q'7 ++:r-N^AݡU9$s&^;jeoo*/90w>ߠV/ߐ^ʝo/Pndog^^^ު*")__ô.8q 4*RU$I/sH;g +\75Hާsm ˦ӯzՑkD̙1@ZzsC!hQXNt0/ɉP'7ZtE콼*V肜d`DŧRE.'?kX d2QUl.u h`h"u2:Ds/.nLL,+ JvwOpr}v^yOSKtQQ؏-N*|8ZPooali`yϮZP܁(5Q9jh%-:h1sӶCPR%H&piSpK癟2Z,:Ivv}Ym|\eWڤUs/^>JT OxoLÃu,cpe"XoxC^.93n]@ 0qOE\9qA׭ǵoܼ7}R:Q\.Ҟf%` 銠MUˌK橐N0׷6=$<{w}>ACWo|vv5?=[wvSDS4U?Z/kX}Uc{؀nfIu]P2Kq{4Ƥ+**_~ -^ik^RFSIy’.Ru Y,qugY;~8Oph`q,7=I^O `O酜 L5\-wd"  qXW>hyY ׊ [f~{:4"x߹~IYiۄ>zD|?Li΃ -Yuʹ +hf*򼯏5Y]VRҔ_QT[cfBC|Fƍ7lذ~&)yfP111^f9ζ*g&^=8[랶KsrZL3z-9ֲDx3qOEn KBJ6f0f컕Ar  I +^Kw?y >?۱ М9?m>Kv=s a +=r$[B9EEU}9_$|+c%v񼅟ϙ3a"-|^~3|U3lO` ڋ^kyI;رԅn(d@rJ?8sT;CV޲쿞cmEyu]u"|9Tdı52^gkrr z= 2 Y0( s_r?|_go?6rL qy$؝Klu~A/(`|sGܪ{[q-uUOu37l0oU#Ƕv$gzAbbϴs/|n%K_fq5^yɓ?wYC^;h|ޟc/= /3Ϝ:w>ju#G^7t5;]?/kҽ ef+V=bųɖAosk?o̙^uU'N\t{;/_|[dI AyvWMbb?E_p?cBP Nߖ=RG'>E־NitחQV\5mڴys gﴻ="{+~d I`{\ ?{vޣ~Ay^sWkzΙ>|C?ș7޳ιGG!{ߣq/;n ~^|={;޽~lo~kZYn*}Kng/<(?ut蘟\tO/ &aI%Y&>OČDI Q',Y| {nj6w}csvzv]dAO:3bf{7Xʫ=K+W=#+[n;̿Y7uz~˲{{aW]{~;uM6%^xg/_>qWvܵO=q#قTYA +ē:N;{λ|G/>JЖuow+r?ʴ+dܼ些23Nܤ+˫sӁޮXGnU|ɒd7q~ NߖVQþqTaWywQ"͜2G%3 |d۳>Y~}zn~Z 2s팩wu״+&_R?xT,opu Ldkͽfg]=9mW\w믻"ly2dm>6mؼk[| &1Ƽ +xAz 5[BG&ߴr­ON};K{֔COxQ8g\lG+/@A&^ +/@ )0=( jŕD^P IxA}wh~ u=toOtfܥ +w ^_f +^1'D8K}< +4t +Pݿ0a{#3?.cJGi ΚI6+/ +D3HAt͜Wi +  +#4̢{մ-"RY'odyy15|^ /,2 + +n i1[y.Z__ +D +삽|?!// +:d>/D7 nPeM[l~ NoٲiwrzA(^0izQw1^3 .sCOl9v/82P 1`kxc'^VZG I Y i hfAM/%ٲ~lf. MsI`YdÆuo+/< zI3~4Cu{>GGtʘNؖ.GtiSj/WFW}Ο) їƿ/ +7w 5 +(\'̂S^ +kNAY;iq>SQ7t1EiH bkfAD[4@ + x~xj<^ +ӑ짅la w Ԡ!B1Z% +AL S+Zl>Vk 4 ++3]/ +BČ{PJ[1 uie}h[C5},F$j2-?݊s-^Pd +. DA8,1D 붊RT0Ah(q^ KAsjP&/h-ʠ^ xbrNX +Ә"ۜR/ +{Ai#Y̷FJ)#Rj +) +M) x Pu5|Y$^P x P/ +9bj xx/@[y/^ +:DzhN/H 'jgy}Aj/ +/e/(_R x ju +iv%i5ǡCR +x^ + 5|IJ.$ɺsI.a$įޣV-/^ +P4+,%#,uL4.~j/^ +0|oe  +-/^ +bzAYDZ(qm-zRPhUS(S^av-~^@ + +zST"C<*SZ{cav "ղ^ x6D{A-͍giY C?19*0/ +23L^ +  +H2KlDu%_/T p- +njT/HtFZq*֐ -"^ f+b^/´5$9*M C$~njPG喂 +UwXQ ^@ +h?yRYfA-VkESj f(jz %TTJ +r/^ +*Ux/ +?[ML rxAtȅƥ '%|a\?< PI?P?/^ ʱQa }ڬ,ITiY}nxA>/Gc>Jy^ +(^ezUN1./Ny|  R| +,~Q lPڙa! x Ш^P$?h (ʩ 0Z+/ +&\ +;& +TՠUrf7LL۴:^l!7R + +^4xeսi5sDUOGѓRO(y/ +h$] ?Ko0Doт +r΢,fs9dd5 +Bkȼ +E:^ + +R/MDlv x R +{[4 ^ +25em"]SHҦ !ѠA +ÄR/ͼ̊fL4yMrio(^ +ʺ/ +{A, +0LAP + xo +L4 +x/ +2 +&B$9 +K 8 + &' z{ukM\PU\ +Oí%.P78$CBdf=~vEjt " +h:Tʖ +:4bT&ZF|Zg~fG]>>>Y> zg-^5^~0`Μ9ffyn}d +endobj +126 0 obj << +/Type /XObject +/Subtype /Image +/Width 608 +/Height 434 +/BitsPerComponent 8 +/ColorSpace /DeviceRGB +/Length 32627 +/Filter /FlateDecode +>> +stream +x\GO'o&oI4KbbbAҔ4{~]w[<;xgfvA6 ٔDXiCf  {JC=e}X`:S WI qW&V/[qbˍe@)+VY[6֔`Bl ۾4]5,cqqHQW?4.GZO cZRC#@$@A[p>!#۟:OtL<&9-u+)T)KaZxӬSdIGɿ} 9+DR[y\YTu%VYUrp RHE0m^ 5)wogf&#DbpFƯ?8`c#F@lJN.w@ .4:7?qkktIɰIAvLvXvPv@U.Uu5 '+/ zI՚ztnw+]TqSƱkI. ]]"KT.UX_"@ffqVS#yr:q=%Vs|L|ץm~оyOݚu0zDۯA}6uuKJ  #D7b|,!_ggXêW*5U*.@.Y~~DmJ,=7y$Ya >"!>GD̥ɞHۤD  P@ `"ˮ{TGol:kԤtTv +~QEPjm˖֨f?5dAD=ʇtl~]ih0 f2*:/r]~#P FWi asjt3 +$_r+? %T%æ3,V^^.SAp>M~<y ʴN4L=Rڎ锏܉|#QQR9#ึ2"Eۦo+E#>h2ֻ!yDs7 w]I";|x޾JP$ff!㜱"ѿ[bm?OJ%54~(K;:d]3/F?OGEg$hc@Z3 +#nM]=ͯHS>g&KJ*흳m(PP8tCJRc_9AcRu'WS%QjV>GWSa4to +mDK$8P쥥er9̙`73H5) +zuT~MMY<vC+K/.5#7Q$=`ɯOG6~c{IvjG ݄GW~axBE-})C[#}1FGźϺ%>Ä&aPM2lM~y +A +#WߢS!SѶÓ*pGsrf5r 7.ΛM+{i鏙̛Ü}ƬhB=ha-{vܢmF]J]~,eyIʤ)L8NS#%?o#;* ;H"Gv[[ } >Qj|ߋ&HO?̎(]9uߚGpD"% 4uoOUuI:7lE46L^cr+, evZ2s-iDKu{ +,:vz-Z⭑£- NmoN1[$i"t䔗1GtXXWR+Ѿޅ>߈oIޱų | gs{HZ/*ΓUIpŀ낅/iBe2Wwazvl@,R!OݯAN&uu+媔)U(S)S9A]2DÄ7sޜU܉ QEA&*o/=,GɖqśK^eBچ sG܅m\Sr+h&W]8lsfr厮(jfMx.D~.>|x]\1Q')zLv9;ܑ Eqno&8r;y+<)- 8sXXE 3LfaG&T=mhϥ :riL9;T9|U=k|Dz.Gv{Uԅ(SP'*S?M$7EXƱ߯`]A{a}eTv.; %ڏ};nX*{ԯRm~?c,%,jw!8J>~#`lE?zIj{)?o1__+G{ j8Orn6 l0ϪE?F)}$SSt&;ٲptX +٫X^7%8rX@ +f.x`AѫV.>t]B#;pB3lU +JPo{3QKHOV3;9 u_0$yemmڶ[T)u:5kEWU.R'=KL}:Z<u5-g"pV~܉5YzbcٙegeIgdʔOIqe2˶%nM]it.YXDXXq\:?">AQYpv:hʅAi_!Ldwc&8NF¾S~eVs&  9wpz,rm: of ^b霃WJLOfvp]ݜeS +/ę'\=5Yƾ +m{.Z[ȗbReВp+Sc7ZP7d9^-?[iq]6׹3y^XمQ$<ã؇c-G&s9  6ZpsuW)թq6rӘKѾW:Af?U,׏O"oR7SޠA]t:˝ }K#L<- ++Z.#i%kGe.uڗ|DB^$ȑX8Q%v%# =/ev2%$: \؊ܙVǍ9ߒ_ɨmcu,g ⍇z{XBgN0p-c_ s*1yjG3Lq̧&6ts.~qcָe㷛 N npr +ROKߖEÔCHC[mH3nG/`Fk/%х9l|{N9aŜ8(|'ri;3%K,<T D5p+(.sqM땣 bȜZPYyPHJ|; ~RZ,myKSnwm| Ej||O|rVWe,ЃVQqΥZ&8amrG)ދ"RIFɝ0+g![~$sw_ +g+.uך_iRw皾>pٲKYЉpw" + +UoB$%Jref\{"!>Ϋf ho_-"l:IY|8d+I-q1Rmyw*ChB$ҐS $H-|6? +C#K0Bzpd'ϑ8coE,˃G r>d6ICXhu8y![O/ޣJ0LÇ,O!K_e'&S#G$"JmXÑS|TaK|8%_UPނ1B +f7*@ +椫GﭝtoY'gqa醿&ip7OG):F)8tOAn&vB3~rtަI/3`3 +e*\}Bo~,]7tW%KF7~8"O#XT1Q0"Hyn#DqQP\wĮ*og +YJk&%g{&+`q(@K?P:j?뽥_"~8dsauo߱D0'T^Z~RQK~NX56a턄֌O\;.iտ9mF-:}i|4}HdâX/?(MN>b/|p!q/rb37%U؍J '2{H\ H:nJM̮$RvW{}kjQkY/W}*daK, !,mWBf5:3| |A H  3Lf#L;|$|jG\g:MYO,&[zАm$ u ԣ|ZG+ɬ?^a>@ #'ȕ@7D^.9v^7y7 +hD#v^g@97M7K&>v}U,n/;x(WTYp[z]5>+7 G~)?cjmT=DpQGa)(L8 O_k`GU|i|HfWGN򊑏B6@ P_oblih D>;mh2@i|CbG~XS_X^ළ+,Q=b#tؾi+@/`H/W3v8hPy/̈^]{薻0s:ut Y|@=G̿ +@G #uF  Z#lP* P˵ +WWA>JyژgW9E6^AI#%#r +8p2kJq*i@ vG#|>J"wM(A,-|>o^]yM(Y۞czB2` ĮQ +b3C}"|䪗z۳J&F߸|JhKKHyϥȴ7H> +^]-xJ7/腅xdme^>k/ğHΗzGJ}O +ɁO;rasvyE${"#=#-an]AnQǠc1H_54}XCG45h== ^8,=eeYy87)$bV壐m|j`z C$[ЄVs˻HҪj ]3-Qoi;^D Kjأe'hil7UݺL%&6Qp*;TM}?q%S[S͆#JEpDd,aY|DZMY?$(*c1Iٌl[gSs} R\$6GԄ"".4X~NVT))n L#] +f~&\&B.}ǸH(p*C8QFt7vRYm3"B +-ԦnzB3yS+/h;._qF=ds +H2ww6ȲޣL']~[vsmߧY{e6[kjZ߉]:&#seN;㚽oyomcT0JHbF{FR#6x׸MyVX2hPVIO.J(K˪HN/zPK?w,H 3>ggv +.kZ$5uE"v:sKkk +ZwJǽ{yc~~ŋ,X.//?K,G555[@/ܒo8w ]\: ~s󱹅Nh=~ɫ]ۈ┬> 6J 7,<)|.o"2s-X8WSm䒖nj48frrndj"WCLKi4FCNJ +ZMm}11ʯN#ϱ L9;#O >e0TFeX(,ad0r FB]]#v^gMIyv]^w(㻠켂܊脂򄔲ȸKI<,Q5I6(kX^T^E I Nw/ulO]SN.Rtߦyd{f{fILdd${JyYU|7{ѩv#>bȑ6NecF˙3gΝ;(UT'^IX66YyXIuW#g#J5ѐ* #cΫb>8ǦǧCSb;,mm mcczs* B0J6+85!;2Z_'1`O[ϫ:q롥KOuN4D8I$Mۣ\QHPX~S3I~#Hͯ1.)J{[RRƪ*"XPPgeeG#$R#ǺFy&lFrX8,'Q$vN&N[zElWI%u9䀘HхFM͌]㋐|EkT|TZ錷y~O$mUZ.kow j-޴]9pgI#G֚ O9CZq쳘}:fAgmcVڱ.Hl]592.)/617:!| ˩Tבj+k+ʫIueuRRmaIeb{;/Ә5鿲GG&q߀ү(oN,͌ XRk*@پ-R.֫&Әqfx61v>*>N_9T919]"Fg,4Q%SAb?;$}4wYϽL|_N:, GdNBrn]Rvԕ{Z1F!ȩkb۷ccc{#nl/r)* /-޶nȟ''˞NoC#9"8'efu/}S LϠ;zb_|%T}zrJz;ߚ?wǐVĒX| ybg6)iȴIb^xVJQU2ʉiq---d%HEEE'E"~dM͘ YU(d1H56zkzqRNڤ ^ ߲AKwJJVnUL"#C՚)UQ;XxN7=]vH4cs3H )=|QH??r>E٣7^:?3vGE۹G#9> 2zsY-K~WiOIGœGEji);zҚkI$P_~!omҽ30O0[32^qy(aHyE*MKK HOP(OUDt:A is! OIi- aPFQM$њی3yIIb;#xA;^!NC!ĂQ6/;]]Ҳ̊c~Š]S9s|$7~c 2j2Ye@k +,O*o2_vNx{<ޡu(ULR +j[ӧל'>ϼӴt z'͛(lmm=kږfߒ&)9#GtA}|l"m#w>Hu)h7ņ\=jYhy9fՊF?"Dk%#_e>>}:GϘ.scaboaz[} 7͕+7v-_ c\"5d`033OhI_pփwWI]=HY 2bR֊~e1EM4&Zdfl3ͼ6u.9e3muTM)#fnysQxl2/kƧ4Qb+b).Ǜ +oC-5H?٣| J4m'm\ƺ~yA&ByŏF=#FX9ԜZZU_ڔTA.F^ dyHllZgJߍ 4iף-w_r95uZ*B$#TWXRe>jvCbi#iu%%eYYũّɾk.^6&U{l-gW0n<笚s>|doJoh"833eGDp9tw2qeO(䢢(}bAfKIfmf%fzSAN^^1NNv\1{D&Lne:$72b}(9_Ѣwd8zEGVwE=]jjj*E8;o^66;,RDC6tǹIV_zں&s>zO]paKݗS$BODr;) sK)t"!8R]u(q񠸸1:!>f:AF2I2M6y]B_G;ÊlCsN^ uZM3WW5^y;.9ɣfG̦:jKQ\1?rZKK#7MS%4MS3Ͳ" Ԃ BYTV.=W..ṹ塡9>> FF,,|71}1cExW/ܣIx>i$fCztw^QeʬņcFPVKe2ۘ*L&p-6e립OknPp`̶1(h,E&وcAAAaaaii)z$ JCCP߷yZȟoeE.dv]+_͍mssQq~h)̚8z m'O\uu_^i(Ș@,y$"W|]4`h|ͭUi.yZ"?~ biKbE+ggV +;xiĢffR#3Pt`ZPBtխk~PJƦm[{8t\0`o̲ "%Wog㝓gTS%g~K3܏QIxf^V\\if~^+RQ/sDCxs.aF ,6<q^koE$sV|!!!8Ӝ/)FF"N|{NW">?ɉF>}_a(Rc/QYǯccs߽&p;/MM}<>uqs^V?Pzrk-ט__,}wnyaw͛wレ񇌨wJ§S0iֺ:ZeecIIM~~EVVirra\\nxxF``w{4ah.,STT9s3O7CPdqݗcU|nO=r@o=G1$iod. . :1[/;|G@@x]u\HD]:ȗD}]T{]{ȑ#CT ȢePtɚ{ ; dU¢_UPP07{^V5ژ%''H$v"?QӰU{`jchakb𭞞ˣG9Y8A]}`sG!""K3# ^<2FFΣ ݻVv-8&Ly╗yzdrsM D+*!'$GGg%zzqsu׮]k```ahU___WWWKK+W>J&mݿCW?ho"|cdο=/cԦ1)1:7 Y햞lLώG[x#IZc7K՜_c]!rҢIXdK>c|_ϒ^zIRb"}Q9]ćѣ;|}2*#NE;ujeH|rgoWF>>>ϟߒ <4"'}cᕣK\v6f6֦V/^4~iի%Op;pvwiv6?nrV ӊ;A|> #|>J  ,Zeo >G#' +>G@#K@{G|`wRgԷ| V\^NF#?JaZ00>GaF````GYAG000~G|>~<+|>G000#||$7##'.{D- 6(=G +F6ZHO*hḲEgEfӃS-^Q(fBF s#BcO1q0R0Q)XbЪaڅ"GC$sDp,'oIK`pOƵxǒQLl#ǍO",F|dlw$Iq8%FѧY "!T9"Z}p!7]Ӏ```G!, > +?0.H2CSz}$NEyӤ )̒Α5~,qzE`%Fȯ:,"|OlTXmHR",zѽZ 磭O* oN_%x#R8܋/b񫈃J=dVV^>v8`Kޙ*{ӝ@;ߌ]tFV]ie%KgUbH-RưfƋ=p)vѪ^c͖ӝSGT> +Q\1^< +/ȯ;f}4iu2!'%]]CR˄,EN}+000#W;*%lQC|o_z/U)8[sǯ˯?{tҿ,_g Wf6 f=ɼ*E2 9 |>~?0}2j&6kxz6ItWG000ࣨCX8 ϿOQM4O2B@W=Ipr!ڐBRڂRZ߷>{?бG#Xc!i>->(D1# l@iEr{gB +|}GG00 # # gd|>yo #|M>(aU0`>Z8`G~K# Lb$G# 8ٔ,>>%N#G#kJB +Yڇ2#L\d\Dwz:d$~A?w>JF\Hv^#%՘ ~4G#qQTj잃\#Ǻt WȽAGvN`>>L}ik_`X2ܿOnd&*qNomkk +-4jc|<@f;N]JxiXjZ2l”#}KY^Ȼ|; !Ó +xkÑۼwo#ait atLi`Soq-QGI?{0򱵕ĶGJ%Uf G~kn~G7AiulEzIC 9fm2*2< 7C(wCVb/[%BG +x$|Grk¢0߅am; T2oڿm"T{ihTlVjZ*E`Ss~iv6م|6U_nG]2V2(w 64UUWUU+ʋ rcsC>f:AF2I2M64y~ED"|W; k>q>#T+HHNL |\hs|,)LLHHO +O Lr|/P >Twt#zanjb y&; zgim/NDisݯtI謍GPu +ߡW'PF1ttH(껐GhS>'λoU#@b|Q0_nqȐkeoo8g ~H:璭|*] >B*b> +xqkT~`Zzƨ:=il%l]uXzuU7\V"qUǹv8-UI4 2`)8|CJľN% bQ̶6V$Dg+; |8"%2Ns\.$O|qq0¤'Ұ |7x?B Q2kp.OY #ZV%AHqv+ BK) 6|O#[VůT~L~e?y>2"{f&>W~pVcot3qc/lZ#4J(̧$r.5"GhYH|d򙁼|]j>B*`x%̥]O3M./ކBGN{ķzĵzrGM>rp(FcM>B$Z{C>v` +^b~H#>Fv}^F$EG@d5{3QU~h/(# +/h +ZKwϢ {„&N\ >])p!*}E\b,k2֚OiSQj|T,"PGvў6D2 2&63GxDdKdX?xcRnͅ[l2)s6eD_Y.!?|t4d fKe*@LN8࣋|$(:fvoDnL-H}<%.)e'9R\s\bG# "9#gi(>J؊p`Q|tab~xz>c&&oJuTcaN<0Ĺײַ(9h&跚 "Sd>ЙĤmN o3BSx?G|0AQ29nQ})B u勍 Z֞(Gt휷_ M  +I +27IYP/t7DM0(;oybs5M#-ʝ_'!K_[`o. |c͌$<>j-# + Q +1'PcvٳJ|cad2WxE[qlsCYcɺcsc=gF̎莟NTEM=}^̯,zis§ΎrjY G|UV=࣯Iࣄ^4Ձz@t#>6VUv"' .N~)gȷ&=O"ϋ`q.qL[1cE+~ioG[O?f]/ﭟX3/+FRg\ҿ\YtDk".n0kEU_Ηd(ҥB >>6~Gg ϘW>V۷}^P*>R>-)](zIQ!@"?G_.=+іX~|w'wloεIۢVF'FƬ[&%qvf|}UTH;%?VQ]EȏBOG>G |>S ->r~J86:gZCyȂ##>zth3𑾉@: +iGQ]ѯ'IP`Ҟa,l+P谈9=##>zTYjzWΡ§CE@!QN##>z +A ۩|'?(^cMz A_q)TZ5"pO##>* +mԕJ QK&g!E|GQ9pap4 =qJ?ʔb ##>zXՕ,&'G NZu(GQ ivFZSmGWH MtGUGW|Tox+,>ʼ|BrFb͕#>^\Qo"yOlL ljDG%X.\pt#> +ɋ䬠ҞGGJ_bl1˚ħSZ#qqTTc,U +GQ/%9($>"?'ᨊNeGK>3+|D~GW;: q GHGy%G| +>bGmGp)HNסObjAї#UFz8u>rGW:Oh>"? +j>9IF~ЄUQȏQlP*+voW**Z +)\|UGlAY 91W +pLGRSVΕHc{duw?8II +;Yl>*^`'>6GfbRWR!e2>*ˏw˿([lg#VDWdfʈdYto)D|,Y:k/Wl&>d.њLcQO/'Q8;pU$A  +xV.>ꔛ o\y(|%z'a0&2.zuGIG7$?}pdGت+VgVfTF@~Tu9(&DBG2"Ҿ*P|2>*])÷[T~:O>J h|afQ|$!(;hlcB06r67m.lZf"Z-2'lPfNĎrj<G *U-6C9' Go񑠐(*|RܲxS<ܔij{_+%(t㬐 7 +!~ũܺ$M!%YH`"'iXm ` +>Gh636 !|əRo-],víifY&AYd∔SRvL'|_ yytD[D`Y=L\@G#)Q;羳~䠅j,L"y'~Cg 4^Axs>]]#|F8B Ga(1>:N7p^iЮ#\ViQd9oǿ03d+!~ "~SenY!\fOzIX|*B: X&J a#{ntuzʝ_'!K_[`o. |c͌$<>j-#iPK!BCtՋՑ4,vd6&>km-u-ڊgO֕lCgF̎4TNTEM=}^̯,zis§ΎrjY GuGQr}iɂfci&SZSn)f,||$Q!)=ٴr(&} DTvV*;URxT~Nmꔏó^[uۏx}'EZ88 +Goly6u(t +]\~*1_ +|dӖaP^ 'Ǘ8~? +X/9vY~籧VN>u C/BزC蘄4E+Vv¥4ENGnbxslbbf'qn.lN=Y3y84\cCDx={ہi3f|e߯>X?h "as,2ϡKSke[bv(Q^r>BGƨwLg%>HQEw0|$P`xKx]o]?]⇚t/>ߧWR[jSϭHղrWSަ1>: +gE~Gr +isƕ4zyTJ| s"АuKr\,eg% +W%ȯ'AkfSR-ȏ#>4CY(v;;ȏȏ#|3N{Q>GQ]}JAH5ޔPL>>Q>ꊐ~X?ᴰ]U9;QGO^#3Gn*LGG\A _'rMݪSRQGR&5VQQғȏȏ#$˝ַTNUir=@IaGGhO$p8@zIo|Tx~{G7!,#q*GGGղV8dٲC!*oUJzl#?͏#өk9@|NW!Oȏ#[[7N|D~D~!niUq1^cmuY ?"?a,8Ib!eC|D~G*WpFXMFH'j}e}"FEAsz9(|)?]w붩eLdZñ]aRWn=.n%k^>B6e |Ո8(hY(>8vt߽u\ኹT{\j.o16JcECdͺ#D'65<X\z+<~G6D-|$ɑp̐Td*ZxtfzeJ!grǮt=Nȏs&9\427 ՚xڒsJG}^Jy3,NJ DLZldLX\c*1Wb!4bxD~Q~q&5If}vD|@cܷcHHHL0 jG%0 >g~$[Eي}e+?y"**&&6LDWF,' +dͥ{K& +ca0)8hcIUǴ<J9‰RȏrqSZrCUq5 k7&h3kWgTdTJq,D|,1eMPY5>B$@I((@% +ɚUIR]Y6cY}T|6832Ω-ׁ9@ hlc?, or67m.lZfw-ӖHqv(3'bG̊u954ѻ|b{"RܲxS<ܔij{_+%(t㬐 7 +!~ũܺ$MEE>fk, +&BnNeŗixꎲo`(dGJNks+SWj4㨽){ #ArEHM|h QdR9a| 3B#>BX80Ar >0AqHEyʄ#y=B*# >BuD*sG| a HM|>#A#># >B>0A>#A# ƻFH]N0A>#AA# >B0A>#AAG |GGq< 9!G| |9N9#A|dP|T"G ` Ȑr>B >~GN2#A/6*| =G8S|!pGGi||X|j!G qr?1GG棨n-\Aࣻ%#ҋ| |k$G 3|H| |I>: ! +!}-ml|$>B>0@oEH |d~)b >B>dHH3>B4>H >#gKh>B> +)-?P ]́AKmvw9>B2tJ&| GQG WD| |G| G |G |!G| G |G |GG>#A2Gў0 5>*0M!Y=|Ϸ2L ߛ9NߟU7LA?u?E0^_'ɑya=iaGaaa|aaaGaaa|aaaGa>bauڎ+ml50!k^TE6!Goð{\u6o||'V :i$4:`gNܕǧݼX~\g|a},qb%Aǐ')t&Yy`# j#?J!>#I_նݥڋdBa廢(+>3MVm4&GﳥgNSoDX-'n̢Q +owM Gp?>z]θ7ntvww޺sw`Ѐ@|##S }]>6zM\/}th`'^T4+ ̀oXpF-%){,;f:#u!>>1|뇭N78swI>0M!IkHsZ`>Y ʹ\"ce ^9PAbff%^9d&h~2 +3r.u~=%{b^#N ߻$QRm@/=M磋";H&qttDǤݲEFF>}@۸"n27XԞT B+oy»*K7`QToo5}1#'O:qHwFԱH>}  HL866*cnYgH7.wſ-=uydV_*3?^h ˶ZneҾ2.t&Gz+y(P@Gz$Qx>ʛ ϧ +{8NT>#V#M+A[-"XKoG8-O')Vaf&? +6HR޲FzzfX_p}r`=;V fm#EJPR= |b+$m>5KGn$ߓ鏅i_ dߜ}b-)Vex]UW%߲] e[OF&!}3geh613}%ISc|jU j(_Q>U (>rQXHyu?xjZz, +MK'Q6.ѭ]_l`ҥo;Ҍ?r 1~Z.£{ +Gb1Εe4W]/_֌Vlc0[?Ҵ⣼cFGi7h&pX>֪p磨{=-ˁx`a6,5[mcޓL\dxU>bAQˡTu_N|T9}ak1o%?y + sMs>_ + GGϜXٿaߝޛ{v6G4~BC߹sՋn?g2_a|ĉU1|۩ܾ}Vi|sta9'V=]==׉{-4Yz WIstG# 0ۢ:RsW~u+u\j"&.5^<_dUΌ0 l_ƉzG{qiAlܓuTMgN棤|aVqb}y ߚi95IN;<}j+͙sD# 0du_wyh^gؘёK9ass9a?>:ȇu>0 03&p|k|[Zlwvq&B<]_jc=.0QoO{Ŷʸh='nn{}ϏhgYx ysO&nQ'p;\1g7^T"=m}zsZ!?yh:f!#v jyaa`&endstream +endobj +133 0 obj << +/Type /Annot +/Border[0 0 1]/H/I/C[1 0 0] +/Rect [211.7625 514.8841 218.7363 526.8939] +/Subtype /Link +/A << /S /GoTo /D (figure.1.5) >> +>> endobj +135 0 obj << +/Type /Annot +/Border[0 0 1]/H/I/C[1 0 0] +/Rect [235.6127 281.8957 242.5865 293.9056] +/Subtype /Link +/A << /S /GoTo /D (figure.1.6) >> +>> endobj +137 0 obj << +/Type /Annot +/Border[0 0 1]/H/I/C[1 0 0] +/Rect [264.1552 48.9074 271.1291 60.9172] +/Subtype /Link +/A << /S /GoTo /D (figure.1.7) >> +>> endobj +139 0 obj << +/Type /Annot +/Border[0 0 1]/H/I/C[1 0 0] +/Rect [377.1716 517.589 384.1455 529.5989] +/Subtype /Link +/A << /S /GoTo /D (figure.1.8) >> +>> endobj +141 0 obj << +/Type /Annot +/Border[0 0 1]/H/I/C[1 0 0] +/Rect [441.4797 284.6007 448.4535 296.6105] +/Subtype /Link +/A << /S /GoTo /D (figure.1.9) >> +>> endobj +143 0 obj << +/Type /Annot +/Border[0 0 1]/H/I/C[1 0 0] +/Rect [439.9155 48.9074 451.8706 60.9172] +/Subtype /Link +/A << /S /GoTo /D (figure.1.10) >> +>> endobj +132 0 obj << +/D [130 0 R /XYZ 54.9921 757.9346 null] +>> endobj +121 0 obj << +/D [130 0 R /XYZ 191.2651 571.1631 null] +>> endobj +134 0 obj << +/D [130 0 R /XYZ 191.2651 338.1747 null] +>> endobj +136 0 obj << +/D [130 0 R /XYZ 191.2651 105.1863 null] +>> endobj +138 0 obj << +/D [130 0 R /XYZ 442.1726 573.868 null] +>> endobj +140 0 obj << +/D [130 0 R /XYZ 442.1726 340.8796 null] +>> endobj +142 0 obj << +/D [130 0 R /XYZ 442.1726 105.1863 null] +>> endobj +129 0 obj << +/Font << /F44 102 0 R >> +/XObject << /Im5 95 0 R /Im6 122 0 R /Im7 123 0 R /Im8 124 0 R /Im9 125 0 R /Im10 126 0 R >> +/ProcSet [ /PDF /Text /ImageC ] +>> endobj +153 0 obj << +/Length 1218 +/Filter /FlateDecode +>> +stream +xڥWMs6W7jBFn;u$@۴1E*e']EIF-r DRkDZjbPrD9>;A3 N_ћBDXT4{H,>N',֋$ی."H5@Zd> endobj +127 0 obj << +/Type /XObject +/Subtype /Image +/Width 572 +/Height 402 +/BitsPerComponent 8 +/ColorSpace /DeviceRGB +/Length 19118 +/Filter /FlateDecode +>> +stream +x xEC]DKQ.A +ppKXE@}zz>ߺ +0 G`ڱ5Sw &:{.,%΃a8MT"tJ)B(Zk,*6־z@+(,qJ8BN'&Y$) 0=AIv{6j\*xkI┏S6|XamyijHo.ma +E"%uiֹ֎IĖ-son)!熩9Mnvo+^薜&ർ4so'/WZט'ux9aV +As|GY}f\+L +넧&=c{X_nzKŹ}J65/쮉ٝMNn+~vY//*≍*_Vyly=ywBkҳk]s[^uvuvkf.7K?~蝿D l-{zum2VޓsͲN]1ɼd|μ uB YĄY1Pc]][Yē&h0dJ2b0 )޵y T +^SkkܿJ,l^3T3g'Kۿޒ+}r[ ̬z^ sIDY\fN|eK ~?_am:8پvmKw;NxЛ9Bj^{!' os CxD9qWO>7]59jG'FqʇŷϷ_ivﲼKz.17y]fYoni?5m\ SrLν~RnI6MzR!lZ7XÒew..1\YKbS?G\9`eϚtG;Sc1K$"G̓R%L,i3[Ν) L}Le5lj9n~qkIY-sN"o{jmrEg_k%9H/GɷYEά ?OxX-8o ɧxñ G6Z- wq9y߻vCދmNm:ͰvH0rdu,MDȕӚ0kO)X ݨ?9* +6Yh1`8*m73{r ZXplϱum%~ VSmZZ\!R/ˬ~vM}N((|O51g\]VnY&0W%ss[?Pì f%,h]5pe哄_/]]n1liZdPڮg|f]iv튻nWmܺpRo8UyW?uŐ) +mrNy_nqazט\ָ>G]]vw\)vG9׎")͋Y8Ų28ˊIKc,q],:ƶ͍i=QmVd@i@N'~aVcFLnUFs\ Wzd'cSguzOi6|ҵB9ka=@b$ ߥoבsb_6lwd) <ātj0zrD"Qɫ$@dV_RW+)"%"4;l|>ֹE'u_XcaQ. +;+0mRAمf^3誄S fCl,! N;c׹ȿ vY5h5;pwyq6m8Z϶\=zwͷ-¨Ts}(ʲbruJ$,Nhp%M6 kI@ZMC2lݙ&0WffD` ̚_0fӓ7t=EMT|N]G^j!˻΋W ZȿIoWԎu|eJa"-׵-v3E7_&gͫݩ.f]BZ W6{x葔wK@isvy^`VӮq]׹s \g5.*Д^nb2+Th$uNd^8&wil uZ4̒<:n`ބy;YĤep ̲ 8X_ [G74-05wbwUMVMJ"*I겑;LϿPll#o9c'yϝ$|]!zÏՇ3/#=VOz.vֽE>Z MxHbFAnNήk)U%^UY$@sAL Z*؀YJ; |la}E|i٥ޖ\YJZ)m1.O(mW$hhj{e6Y{;=|Iy=fk:\wf=D͏ _|K5,YGޜ5힜 )wH¡s%Qhۀ+wto"2X-a/g/ +6Q&SiػaVF= \ nؔ\M=?gy#opMڻ[OS,Hk}cjf0A&p0_<ᕝv'u6Zu '~S;/~.%W\ SHTE$5ooN]Hg V*>nH'p;qFo$F) +6SV4I[)Y[γ_=~et{FCw.r_SYW--|q5>7M;g'rS +i$K|!?XJ| A巼@Ja8Za#8e{V37qqs|wl1sbY-3zXvΛ>o eqeΠ'4֥x{mW0On ܏}Xuݬ+򯚖V[ 淘?L/"鴂 '6nS,sQuqzV癙]fe:3SbVĬnmnhn9+˧^6%Alidj/ܐZR]寃o:<ѝrxԭGFw::㱑7yӉaOiybU'ݗ`O7GLw\¬/2Kd~%,OUށ2lTWksU78K84"ԕ]!屧ƞn{沘; nyES}us9S5~͈ouoZ}ݍMkpZwg#;Gt{{^Qk?Þ~ܻ-jgO;dGF`뭦?R fex%rԒK*]\5J{~U'ϧ|RIUzU +Zw+0ͫi[Jt(!G T77zgޠS# ,;5epÀ(l0~葡 =K^6Rn.%0)$Qc?sOQTo_H1!DKG +pY^@ ؒȶ=@RDQ!)b=L'[]Zdo.B5fI},#d%5H9JfQvJ|U4I H)vv|('aaEC1=:E\Sfm@Gʊ6}W黂`8HaVc,_h,ٷ~C>wr>ّ 3&0K +fo"5.++|`V^ÎYӏYyUICS0*m'AjpRW'ۚDCA8a2}87ql (  + X(T"\,9s8>E)s0ؙG0e~i/tԸ5ᄊYRs`H1,ʌ #Rs0Xb ,Lo' +o1=g?4iT^Ϭĺ2+3σ10++0DV@E + fʎ\QR㒒|10KdZ#f0mj&Q<Kga/` [rKJlj\\d©eV&=a5 /[rs9Lͳf +̂a ͬ,âaa0 a,âaa0 a/T,DO%ת3ְ64 +Ud YMUEy|M +I REEC˃`&=zg ˬAgM7,j fYjs0-Qd8!}2񊨚N/E}`LsT/^ +mA" Yis@~B,|Pڦq \s7zl6ccnp ,5ɐ`d rĒ:og(m |3n4~jRKj+Z>}ķƾuCVHcF6n@Y{RpW%5r/|hOgJ +YZCQVYFkY +߹JQh8AGr , 7+9-bۊ2+Y 6u*k7Ih +cLP 0aAtoޠ,doVHYRh 7=]f<:oPﶂ{Ȉ=٣n4A+_U9(bh1E=$ߠ0,E# {u2 m_9Tl֙ewVY?   ̂ ,0   0KWfM1AYhYAPd0+81t (@{:b_mLrpGRzM.1ԖzeB| +eh%02jZU_-f +ZYaTCuX@o,0+zp~Pj}ue/H=YwJ}T +>f1}:հ#RX~=Jا >.Jc4p7ޱٰfrw8/zotf+}±RY>{Bɞf[X PT]Pt0bCk>QfJ/>"2+!g{ 4mw +A_q8 +RY)Bˬ0c,dD}GR3pz|forS)&+28}~{1̼A t43ʀIY2~  q{d fm  +̂ ,-AfAA`AY*S AAd=`A̒;k,bsw +fY|D0+ܙJ˘F*fId/gkj/mB,R%z,Г%&—:9wTkX +#fQ4ɮC%$|g*] ,,4:fi,EKG7a, 7Az.u)Iیz٬pdA +,   0 ̂ ,   0+)  O8AAa,Mc*}Fŷ~OG쫍v3l,5r@񝬾, #f5= Zi +F6fgʢZL\FB9,bfA,/k~˂KbsJb7ڠg ߺM8忸J01`}NP=h@`zfqlqPVwFC(J%oj)}]=ϒ#iA(J#%{^eG f)eF)X`ǝ2ȞPY60LO,({i^ؙ%i K=Q!lec(bbˏYAJ;Y|k8=cL8ޒ A٘㠜Td; UgٛRƱAO(̂ HW|C`AlоY` n (Ǝ,0 ̂ ,   0 ̂ ,  + fM1AYhYAP0q}O"@|tX*U8SB^Ѡ,D:%%>, WrGR;ea44`摡/sǞ"]ih-D54[LʾY'PϔջFyH(Vn(K݃ +Tnyf;8 +'̢ OYi/WxܢˣdHő@F +06_ +'Р(#׋Yӌ}lylķ-EZM<-ELibYJcl1b;*8FQv,f칤gVafE'8ڨ`H/ҚG>b{ɘ|5,0~$s2Y-T#F`{kCIA~,Y3Ki,C1 k0+JQB,]%\QԃYZ|˲UJfi2f r/XؠNCa5EsnZ,YIuxfqL0+a7o2E)f>1h*yy{C`Y~S,5RG bF+m!H/% fAB ͎eBQx,   0 ut&0   ̂ ,0   ̂ ,0   ̂ ,0   H)YAn#AYAfAA`AYAfAA`AYAfAA`AYAYM{bdVQa54ʬJE$Qz A[; +K1[Tu$s>N/q}qsӟ:r+YKȻs44 &f湇FpbIgӎ2$vLUG3:{߫wޓU{R9Tk|KԑȈPho~ +$X_u`FJ"&vY a1]g)aQU>/z/s,0+Ucֽ ]GwGcjKEjէG:@}v/H?ꯎV}ZM}NG{PS/;{IbT 0&|61Yxh5$&[W[ǬOvȓে8/4Iz5Yy!X掟vӉJ8 &R`fϺwۿbסC_u+??xRAbu{2Y:ș䄝+3 5ƶ0`ͺ]H,˜X5O\p)=J_k]4yM>''y.r'ߴ +_;ۿšIF6I64=$6KkϢMe1zGXdaCPdh󞜂27dnLǬRݙ8zԧ6_ؚ ɿy$y#[bhچ`S߰bL,az0 ao0 a`âa,0 a#f5 f0 f4SM`Vg9~(쁌q=A$QY~!#iD‘M8 fEI9<'ҭ$]OaO$ `V1͝>k:L;asp$)٥S +ҵIu)0JN]z8|SF@7QYBjw/ 0 endstream +endobj +128 0 obj << +/Type /XObject +/Subtype /Image +/Width 572 +/Height 402 +/BitsPerComponent 8 +/ColorSpace /DeviceRGB +/Length 19444 +/Filter /FlateDecode +>> +stream +x xEC]DKQ.A 3CXE@}zz>ߺuRQD\P +?zAD\hbB&˪,=k־ֿKLhpVVeMל'G/ Pg0с;a4aEm%~[dqӕĖiNKcm2e|eҙ - +E~5ZnP@u>bq7ĝMV'4Hc cb At T4ZjA222oZ,=Sdl˰T4¬)ӆ{2=I6<_\~IXD0 pL{^9 8#?L` +{e;,8r11ǜt)odkTY_6WIy#W Tq<+^+cM KY.q߶6vd5WO]=j"!WN+¬!?iWt#^ +W}* T9Aib_j'aEvyҤVȿAhAr0_Hf+ *>KL"YKIU 0Cߠ;,cF̼MIlzDUl-'ZM :{S޴2+9˓z&?_gwU>+\}"7"-nsfcdڹ53l+&!=_n{cV\wֻeB3k|PY3+䪍o""̷4`VUK͸YPvha¨)GqtѠŏǍx;>|L#_zߞW<8܆no;PN Ջ }K5m +68>\%N2a0,{.|f%uNʻqV^ymL_=brޕWM$ز!S9VS}9cZ2G=b u[_E%0k~E5T>YfkSwꚄ)d|T{L +*QK ~ӡvugS F+| 'nز{~GHC Xx~Ba~]LN`x;BKm⹢;E w;'~)W' Sj;8$"Yۨn4~,E"i1F !S F\Qo a3ؒ\)[+.KpNr[q3$Zwb$gSJ; 9K DKt޶okng]y ԴfVXJ3'ܖ= ]0$w@ܻɽfvu$D{?ߔ7r20.ھ,|[ D`51AU'd9mJME,,f +)&/i/7㒶 +-6~b[x@ϭwdIh&# c_DE<_~-"\# Sj;8$B7`MHh;`!18XJ| N7@Ja8Za38e{ 3]q0~ m;f|wy˒]r7It5l`ȉ@H2iWq\}vzo^{K{'Nm0f La쀖c;eN=9$&mM;mFwNy卿4/d:7mjfA`u +ld ZYISLqc +t-Vj2qM;6= iH^amE|`&j:eW']\vܲ6sˮ$n=ܲs8 .Ir5hR8eHjʿb Bi|a]ܾ}\Vsym+˺V:]juBWdg%,K]̪i{ڜ巉O1OƔ =3n˚5gR㮵navYΠw5Юffx6Y ߛZbkҊZ] ,_Fɋo|l#>0 GZ}u٦q'/%./?4t3hn0WˈSOnk@ș. _vϧW_Zȯ/]7=7w>UnY .:yd.uW~{|UjW=qG]ɭvtmKOnn&ˇ9^r2XXhpj%zYhmW=V5m48fW 0~ϯعS>JZoJ[+ST>vE5yUb #)iX-0pfE$q* +qЅ%\y4e2+lLq -&˸bV=c -7~*5n&ZXm|V'emX 8vM2V Ka'wN T4P!5lDzRǠmK8w8nMiٔy`aT.?cs` 0 f0 00 Yâaa0 a`âaa0 a`a8v"'PievrOH d  f0 ښ%7ȣԧ,֨tu="f] f0 r&">dibcfiE7w0 ǬC2:$B0Kf bR!ye/R_zS/ +N_Jyi%p y_c" Y;~*#xjv LWfY.' \Wzԭ|w&)Ri]-hU<+@d֖}%[~,x.cɇ?ͬ5J`P*U+)fὣdY,0 Vf?許UB!f YofIUnHNz{kXέ'6sE͐_] !'a_>zd(yBRK =+]zD2?]{ + t,ES.fl"7+R +>% 1RzvlG{B$53{KG6,EI`I.Zd|R}]z"{q)'d,cg3Kj],){X>R,JxSU!bhHȖNȧkf|&`), +~\#1=FF2?@Gʊ6}S#Yb 0+d rn1\.2K#ϝO'`fo2 Hg_:*,R㲲7|f5y:%1Q>4Y}:'5}|I4d,,>FkE|d`H T`o2@Qߠ_r0 k#A_[ rg!eY0 ðVqDhe4̂a5”n{:͝>eFI ks\̂auLqaqz=垒r++=!pLYEyY70 acVӞ,â̺a,a`a0 ̂a,a`a0K rʶdYI,]X&tdT@2/(YZ(K`oHaK;q;E['fI,LӼlhX*Qhg`V0K1X){rVњא,S7\]edмH/QxYƌz-[/i\icG ]}k|c0qP4|S`c {9~YfLcqGI!7w0 YR}8` 0 Y0 0f0 ` 0 Y0 p10 Y,m7Y0 pT2k`aXsĬ|ǯ0 0,aB0 `qG>ɁâQ#90 fљeXdT%/UZK/$,Jf# fߡG`0+,3a0 +Gpj**BMAcVpxΐ-5]UH)~ +UCW`GWP.lddEv3KQTOXٿQ*er/eyRIzoa̍0N,!WM!ɥ7YRXcKU\laK,9 [+:7 +,w]R=jelO]1KXj7A2[$_$xfibNv&'*bS {adGA2Y~CN HT>DY;${hR>+u%AJ5wժwjS?f1wߠa|&]䘻%uQZAi8nG}m*r,2Y,7([WS + odIq1Gc`57({8ډcܠm =aœZp?cao~YIeK̂,lw4~P3%YAfAA`AYAYM{YAuV%~S AAaa=`Aһ-r1PqdtK`ORSoKhZ#E,P6W#YaN6n|, #C&AE7}Tr*huH0+Fa,0\c(ZʇeYYdh0Kj:|3.4~lRSj+>>} 7Ǿy[WHcF6C.@Xkྕdؗʽe5q)U +Y>ޡ( jC}/GzߠaTV8ܬm+ˬkgi7ج)n:F`OL壾 0vaAtoܠ,d/VXYRh c7=]F<:nPﺂˈ=٣.4A+U9(jh5E=,ߠ0,E={u2 u_9T4m晇bewPWYX?   ̂ ,0   0KWfM1A,z4, b^Ϙ{&b=6&{z`81#)ˉ&jI_4SJRfi8G.B^0KȨI)ձsSDJTmXͿ̢̄L~~0+vvfY j;WgWt|у}^Gcc ٦ ;jn +$s/OXAaF\;{xf,ax_+}B,5Xq71Sɽ&j[ +A5ӶoǶPtu 񙊮4!2Hig /&?O ( +0 efa-2U-k?+ȰH2(&f "fAAAY[ ̂ ,0 T d=`Adrf1NGYu&=@iC.lcZ8._",Fاt袬 +#`MQ3)502,i{K +K*%:gV,/f2zWti 2KMJGnPX:{1n0"c7n~ﳌz AEi{=>qtWig]RwE'UtCeQ, dʘ̂ (9;0 Ŗ YAfAA`AYAfAA,)  _$AAf`| )Mdk3D 4_AjJg"Yw-hȐNޥfj(adԯf"rYh f&uQX2 49f75Q1s#YRI +]ڑx* ƖT/UgR,iمYn1E/+fqbEqI_aq"b{7YC=YjnbA$րU20 +>o|pC JQ (6Ÿ#8Cةf; #.o#ؒ75הc,=I3+]>Nz=1ƝP 2ƾEete_e^cYW$k>rFSvnf4ZS0ٿfxDIe.cgԢ,1WJXD+nfAQ,E<S+ R̂,B m0K G:$p +0Y5YF,5s719}Xlg {%#-cY,7n^]\ QGSG0{l^hÊ H+R#7"0fAV(jT5v`ATM+cnr  0 ,   0 b EMAdfqS[ 3Or|Z̙8dz7ahbew9(b\ }bRJ# E74 {*"U5_A]OaG9u8Y&fE fY`VD0+p}rKMMY_/dN[Lbf0(~ +ҟ*л eUܰ0e}ǽRǃYzU `ҢtWd_RDM$}b.5lռVa9rT/Ɲ-53Zj>e1qbᑶ۔'xf1VhZX6(6~G"j'E V=WC󭔭y,*/,elI-G_Q+䅋Y>%Y]L,+3N y@eL,Ea +Œtf)fOD(H,Z=1Ko}9774og&1,Eo+TZB:rH4}dnEݿagw;5Ƿ􅔚/B7w;GaH2'=Z2Y>|e ;5,},)U4nPe߸AgƾA5 KtUXF) }XtNcu2 #inz i(0 b>1g +`A)  0 y AYA%ɬjȅ Hao! xfѣfAA,7qSX˹(=ֱ T K1H~:h3K%ӯ +ʀ*҃Y2*43+tw\_0 ̊f':Fjeb,y3KZu+DvcP*w39(8G*)GR)e"ekpX|Q۔ +,a#w Ү* +?k,ի5\NEiUa55~ќYꓩU!aNYn1nRNMný1UtĵXɾa_tOuQ>+d DYR<2bY>5 feԱyepYMM,72lӐY:ݭ|RQ5gGA2?bYZSvI /T(c/>8Jcu7}laO`j{E5}>[]IxhØ7ww17R?5ܩ}qe,MQ ;o c7;p}Dž2=QU=(bS8Q=ta9?a +/\_MOB1KQJ,/om$`;+Ffbn\_͘X "K`AfOߞKo, Aa-Rel 0ȾAT0nOM1jlp{BvMaܞq1묥[*YtTQe-ivߤ^hz|N/,mYĤpYf +XvҰ/^/YP̶(LY,h,~,0 vVl";C422KxpͰYJ^jsLaYj쮷!L +EYZsQ 4 t$)PZsٙ^G} ?DJŇ{ÒjXY"L<2iS+Mͅ&|qIُB2K*zj,0+Ʊ Rd +;tCSpEwҪ^U2K~ +0 050-`^wVfUWW*'S 2^ߙVXt0ЏY$ܢ#UNUwׇ_t޶ו[ANB>dIf6I64=$6K5˯l;*-a90+HL2?Yu4٪CV}=Y'C;9?es{OT?If6I64=$6KRr/?nYs`BWftʮ:[5)qsT!&`?_t +*)NUkh!L3[ӪC۬>M, e^~|q) ˆڇPЕY9I)>ԑȈPhЯ~ +s 8X9X̿%d5ipZK4Iz5YEB=kԪ='{j^'YII1+*SW^bI ¬sabRN>`FJ"&vY q1]G)n^U>'zOs,0+Uc ]{7G}j YjG:@}N?H;/V}ZM}NG̘{PS/+{AbT 0&6!1Yph5$&s[W[ǬtȓLJ>;/4Iz5Yy!QX;zݦՕ0LqHLcS0,y7v~SŮC*eYȑ䀝+# 5aͺ]H,˜Xw8 Ś ]kvg?~l~hM>7G\yr'ߴ +ȟ;;ɥIf6I64=$6KWE 2K)&cmn0|69n'zC>|"Hx$YCiPq Gc60M<{2`MuuqEϝH ӑp$0}x\5vFҒ|{C‘O8 fE#_n(LYuKKKLLqƒs0Ȅe&cQ4O8 fE#H +?ɍrr{,\%%y[\ ,#wz';䏩S<0}~FSäw~GNgqq=/WSps[Mz K8GU?fKe@z3 sF.$UG2>,=*v1K H 0+rg7x(fk0GO6uc4Ia 51sJ&ل`V42,pW(GRCrOW<8eeEvor5gCA*v^Α92A/5>2 u5nY`T${rSG +dyyz#YJM̢,`3_MK KK + Li¥bp̑**ZK8 fE]\TZZn3p!|knL Ƒ$(Jo$O KaI8GAz$YlQ㒒4 G£<0}qqq^#ۚ+C‘O8 fEj +#\\dw3sNșH8 cVA~n~@pn$$Od=.ⅥϾv#Hx&yԧ,Dcu^Ola + g ӝ6).{ueGW|WYEyY7$ ل` 0 fY0 00 ` 0 fY0 00 ` 0 f0 00 ` 0#̺jUVaaMu7̺lԙ/ECN12o 1{Y%)$ia^:E@`pk%]gM=bwf*L.xzx@[}SسK`kR`>+poMQA w!0߅0 0lz?sendstream +endobj +145 0 obj << +/Type /XObject +/Subtype /Image +/Width 608 +/Height 434 +/BitsPerComponent 8 +/ColorSpace /DeviceRGB +/Length 34785 +/Filter /FlateDecode +>> +stream +x\Oc4j4{QQl"b"H4{o{+T};8>ٛmgfv&DYDhUrij()( i%Ŕ텔D`[m-Eާ +]m+B0yxYO*.dYe-YKYGl!|y eA,]E\fr15IhV"7@OAA [Oi{"p +M_Fhoi%w1[ molBY~bUDv0OGb-qcj8ɦdִp9 'QB:.H-r-@CЂXʼ[B>-.&K`#ns&BP]zT_`/طh`y97dzX_&|$w3aqr qN?>2iymD,(|ljSWT!3O$oŮW^->O[z6 9M&IQrشW4瀷plN/UBK9O˸H {KZ~eB!?u^Nզ^þi9:#jC}ʌ=J݊m7M9V3rg&Vm*DR|jFœۥΕܕ)~ڡb}E]]tn[i#I.<%zHՓKZ |$jb9$ IdW(ŔvT9BB>~L|4\I>nAW'k@p:IEƶیbW/>A)DNN>Ftz¾ָs3- Ol({xL]%Vx|c,1;wLX^"g̍Nx1;QΉ% #ĠQǥ/Y`ս){(c'_0iy]>N:Wb /0'=/Z%} +ƾm`a +s&#d:lB ,D;FpJ-7W.ї;E.CmQô1h&/oN3;|?Z ]k^p\;&G|\ qb^JD23_l>2e_crl(m&d~ N]elⷉ6'"*".4|O{ MtgI@I2`yl <o} ~mwY@6̓ ++#hWIM92,?wVq^esK_Ld[)n[W'CA6%Ơ,0+5J^`^ U7 \nMZI{qTނFF .)YɎ-r3|?U8͌bxyZey4'xnEd p<ñ +Q7l*{[\H.$ޱ%<r gsŻ |H\*NOf{,$Llu‚4Շ+O, 8éy34u].ƮDwkW\-H}64m iH)?F՞7g$0'X bw-:z~Wm'6[{ndbdj͌[H>(a) G(qԊC=|n.C.9HiE0 ؔ]@Fpg>Y1lCf&lj`<Wklmak&4i;aظ}7\Ik g{r9Z ۉ^x]r1gP#]c?*8m̽JRAO ?t$W$ +Bu)¿OANGس[(󑫴unG+jQ2:6tywdoiOOR|lt2gP(cgWEb-8J|t}/ׯ +Kر-;Y˕8 +Bu[^ͿX<){o<%Bz"d_^!GU|PEt=%A B>5%\zv]gNEec(C#x~, - + IFTOBEPg8~@|I7" +S~mvm9֥ #K..MhQ2# +)>P( +|DP( +|DP(?̴Q( +hhhL^}^2WQۘ!5ֽlx^~l#)D`ų-*HNCRO"򱏦5uAW@@$gXubMM7"V v Gg Mc!Di۷=QDjR Z# +Dc_LˏmŴ[gTR]c@֖C]ĨX$"ysEϦFߺm㇑}ld z󪫣Rk+* +/mܻ^GdDŽRHdpO@S0) *f=YW<= -???_o~H󶀹#cFW=uޟ?|tr + |4r7ohںJ:)@fψȯ"⵵bint8 +;Ȥ8O(WpOH; +XGnjfU5u0YID˲!k6'o\Uiz}G% +-߳jbG.r3c +%.W~~ PDd#qAq2[rv]G>|(l|N=>.$ +RGm/Nq.$wC.KZײ)a(TjJHM**HjMz⛕@.)-꒣+jʛnZ}q&Z6V.N.wcڃ{133s޼ygϖ?x?OCϟrrrf&ё\˟^ ~mI-47Wyu' /.%191]^B.*Cn$XDc4(=4URUPlrn\JbԻ㙹k<~ۗA1ICUm Zjy 1geڏPuWᬜ%':p!.FsxE/B$$7xSSC𱤄QQYW+-5N| (ojjϱ dX[t]#lݰ{?~̕ٳA>\vLA/ VHbBxgP0NiPIa2_t`h;Z/»*BZ+F?S}昬 +cO `.8.ouz/===GN+i>h`_8bԟeovllhlb5VT1@Ԩ!)g/` +[eFgCzV.qam>o::6}x!f>.Q ,Mc̱̄1qM&3K޸oxPu,,a f(;_|YWϬg|,(gV8f)ǟνZzz6 ߝjΉi%J ,v(eJ=MA4[''ǻf{c*1k%* +614\fhe>q˔3JOO8aJ"ErFRoxNo?҃vރSlT'iii=Hspxל$*I-8c.8ګ, ywoc5{2R+*J +{cc3Mյu +S<禞yptG5IIq}Vf4f ʧKLsc3iglQYUςp &=U{s?`R}{_ZWb;Ja%~koJjx͛7û#iH9Kjig=gzhԼ_G]vsa%^co#JN!q!d,CZRU%Ghs8.JHɍOʍK0\}BR|b]b"_8 +ynoGkgPߴ? +wpO=_c^ƌXt]/%5IMn̚uXثXq_^yTDt^kw݋];?ʴN`D3McS@EI  T,**Ɂ?ޤ fd,+# 9+T!K/wDH8нgSNGu\[Z/T1Y#2s>G<ϨFZ;8p[@+trK߯{_?>t욬㞮ekSܷu]6-,,p {T_(juO |LLL_$Y;<{\T<|䨜Gsڜw#' /Ԙl}^i|P 8ϱnz^ʷ|$F2dd|y|2*+ ^ +VE qaI 4@ЬG)++ -)LUKƖ6ǯ=رȆ{aצ~ y Q-l y4>9f_'rJKBvq w>5|S=xT\κojj]#c8䑚/V{7)du&;ĿԿ }H&Mt nɏ]T Tt:NNN611l]di}reCPq{^u߸[qh,'[HS`-b^u%p񵐇.:5#ɇ̼0vA{t]>Tjr… !bocavld$m:x8 +U p۷o'ʊmv5DjIy-܂ +$8c{p묜^yF]]#ʖW#gm{gٺ%xj_yBDDƶA/jRw3 3w1)z=6mahQ)Ia0(+o$4¿M&3dCDe1}v[>̳̃VuZ S

VVV2d >yڠ::zHj.xG#055o,~;lݽ d#h냨u 6t tD@ꐱG{T-*2lce'"%gŸEd?TT1+H#*;0^ظF +lZщ#岬mhkFnt־'LÆoq(<8ʼy ]v9;;KIIYpc rNț%|`fX^]W[Zo@ag]J +D>jc.'߰$h\iTfXlo絁wi9yMJŸٳsYkE/Eu666~UTw=76K8{YaU+)R~l,]IE?(ie:JI8tJyH/ :9辁\Wm{|z:v y/#Ronsj@$"Jj^$ |! xM Q:""5(cc潷g,9JB( +⯮BY ǘ7 ϥ}\\¬}WnՒBvr j?( IOsuz*6KOuKztUwzzad$ğ̈3aVD4J38aBMeu̟@䄐(>rj&ӣ"ļpé^־a&URM,=>قOQFV66III o-11f +iE& r+o:wRR# L<,O#? +٥Y +$}v鞷Yh؞ G} +ftdfU1z3/ۃ?_PKk :ϑ^>oAa\`ak)gz#Vdjmm9֧;,Zp/N_.jdp!#\+:uJpsqt +4114qrTSzH̨:tTYQC9::wu0*zY`{OMI=[769zSNfLqWAKD3cjeL\]WP+oRqRx߽ܭZs(`IRGK ㈈ + ;X?;0;67;*T,N^%RZčDA}nA}xp\{eW@D[.9aI7Ji' +[0aBO +|,..y jj%t?{i98b|=Uwڋu UE#O7N>UoWQnӏ~"<<#9/3@bG).)%MQ⽳Cu"C2 ̣iS [!pqߣBDț{j= HVo%A6*zp\xfEcܿ?x} RhI4Xb4NVk=5#GGfzEEE=cCRLmA!-qS? Q*j2R!1‘ط<(?(Tc7193{юvvVV::ZZlֶ8ك +Oc1B%=%ٳgZmmmEEdԜt;;;%xXϞ>HNgN-튲{"Έo{t=Vɉ1]tt6A/YΗ^|vu{`Kmtθhެ7?_oλވ&~YYG'jEG{O#2W#ͻ#®Pdw:_dScKOϛ/y ._qݺk֜[ʕ/?t%Kd-^ذaaG?5_7mjZ"UڈסbSmX|>YoĦxy:8v5tzn2˥zۚ?34ԫ?촴0 z4S#lr__5A Gy=K_>sH>B`?3wѣ==(Gbp^zJ? +|0pyP^%_>m'|S폼2vss{DsBc|u#61 '>f>v +rc_4a҈FO <7|jI^vt}[B$8KO qoffΜiooSm=nqV:(R>w)Q(aj|TB$oĜGJDʢܔmD}TD\{sZN>~i{6 +ۼ>{FNJ.8 9cc!Z# +DcM6ۈDʦ.>1}l2#"#Dp{| AP(( +Ba* +BPG +B( +B!Q( +B>P( +|DP( +BP(T_#6M|TB>relT!OER|ljzBPgVMD"\|VP=JiZ2%-VШ-Ԗ)EU *9*eJSA,%Vvaz 1 jZDo2ފNDRAt1e0ZTG,itFLiuu1H1iԽzZ0#:B-u 0a-io1v70gM[me~)6mj_͍M @ MMomZoXZD5̠.3K"(q<@q?>Of|D>PG#|R|D>" +EiE! +B>"G|D>"{!),e/ |D!, +B>"9}@^_|D>G?w'q6Q=k\P(g+G  =QyG4444#(#Z#O|D>!ߎOA|D>"АGYxۼN.DCCC>><>rvvr!HHiߐ(c 6E||D>}l|C.#dD>G#(#D>*>v# +|䒑hh=쿊B>"ۼ@#Z/~|d~L/8> +H#ZQWGnI;#^DC> +G@>}/EDC> +,||3]А}p9ow- c'dT@(RsvV777n=è_E>u\ { U+3^GIJ;H̦zXw4#As;/h6z23'|DCC>=?xIZՎưKo"%oR g7d5Gg4E54'7$65G6–QI>GaY| u# F!MB5jKiHkIkG`l +$VB7Ƀ ;bhhhǾd0h,QAt:4?;U>rp^qYqC?MW.n⅍6zļuon\) qM"a |to" S4#/_h\O3bI>=ZZyfU^>.8ukknyzܓ2/Ɉ_8q[c W=;}㭍nqꎞ^Q@ 3x/ #'2&:.G444cWؕ|*+//,+-()JIK 9 ~7}҈ǁǵ?}ҽ&wlKw&ShUmI=j_]Q>Z\#q +u6q6y;Z?|2+=GZ&-6)nSʞyUF^m]+wɎߡ|EjITћ,|mzյv)Lo>ӏ+05l3jG444cWq𔔾bn=>z^.%~߻N8z1;{9}hhhh3# /g#y0&S?SeNO>nF>2W9\X|DCCC>rQ}JxǫiOݦ9i*g `^>*ٟH&}t# y;&C&6!tXGviDCCC> +ۅ#;#USW!!XQ^F5hhh.~GoH fFkthhhǮ~Goh_# o򱹩h%G|DCCh#|G"!hhh!_վ 7~"G4#|D|DC|DD"D>?"hhhG[%|D>!? +G ^[MG4^EGW#%?"\p$Ȧ$&Q#$A>|D>1񽼞$ G#Z^W#;;G,{&b: D>P̂TLGh"#, 1{=giǾnG4a( Ɏ |dbvoc +>*|( %vObwVQʱN*`cvrue|0߁|D>~dZ򱽻|!#r;@+՝|DC>un;h9#D>"Ktہ|Yz)h)?wH|0Ȉ|͕\KxG |Rw8P$P4P4Hm}7k/$sCe#$頉rN#׷ہ 2 +=`KkRW]T1մeSE2P.BEC|Ȉ=rs$#/4f1AA}/w#MJ.G.GݳI67/1*=~>?sPGPNG߱b_=6isǜȑF:w͘c=&,M>vI꫾X#>J}M RpqҠW{?RQ"Vn _&~t߿8-57qvO!q䢄dh>zeUI39RcA臭ys}}7C + Lz${a0|WȯRT󪔹j#2uLIY(V/Vf#)M dsmGGg54q8~ >ruԉ#"sF(|y{g%#>fѯJ?D.̛@$HIGzTGkV)hAsUҷcruG#hcDZ<٬ >r| ^mym_j%>smG陚t|DxDth6G$-G̏H|Dͪ3crݲYՎ #R$UDH0x՛I(MދA38G'ktPD2|( .W_.RM|B-SgK_| iK>G.a߽Vn|DQ*>UFh4` + hN;ȏΏH|H𑽛Pٝ6F$#R$>@ͪXLPϡ6#(dHtjuP>[v5}hKD"?"Eࣤ +T:UǛdJGO )r*TDˏUi=i#ihstD'XPp1th$?TtuffCv9tH ࣋G##!b`g^=2|̩Ti\@QO{$wȏ#DNE["Q +D$G!q_FFiFG+]\cG»Er1ۏ/ J(^PBL vMd炭9|R;if1c]96#W#UX\<׽!ydϐձƕH *TU*enL%Sg2Ud:>C$@+c_\hk+|wgMc +#o|L-[^f_5xӌONwi0k6'/O)6ȏ#h>G}e+ٵl!Q8ࣙ|$(4ٹs`` +6c2NͬxGxr؈̋pF{rqBbeEGԯJ4Ig%SQR7LUN$#quW9ro)&[7w>Y7yfiZ>Cj4/:x?pP{ZbX9HH.h6UEetp`|4Z+-Oks/1juCL[~߀Ag?3sFO "d^.u4"r[J+?Rޘࠔ,"́h>Ec LEjP =]jЯd92|$['2'tw+$~';kidߘ4\ȏ@!k2fJ|GjKKSss}Sc]ÕU,i*RqI!挥S6%cjǔSIx$|"ִB.]a~ps". G .{OzllMF| /:^%?Y~,sc>0+&?r~#<$0co@W-۫|t +|qצP('#>څE?J޷#kv?Ɍ$G&dyow>I,N$q7~AhG!s(i)8J`>,b|t8|ы.gѺ嶻\ z70(t*%f>%.G |tA>A3(aw/>f~bD=dш ^Gg>\1lN̰GDmlA1C3\f|J5 +uLEYJ/Q+b|9gh#5gH7 |l#M0f׿:ݯ;?-/|_Gʱ2&9QR?EEw. >JHGx],JM8cs>?3w?=K@Hv#ՈcI +&ll|4?G(%EG'ˏ|Ex\80?h舕M|tWKBxhuDэ..VG.UŊP>J|"|"G'ˏ#:OZf2gP+#>6#8GE.1|3JZ|tR|t$ȳIj0p"4Ay`נsGjGG|KgBCO&HxB"?MvGG|Ia77R卐zf*q t.>oh`G[{9* "+9*+TZgTUi=ihF@i~o=yΒQ*eiLւ࣋GՖLi| Qi^U`_4ȣ!#}?UzG|tD8Yqp7\ +8׌H?]##>#Dî#5QC(^HAGG|iXߢ'\w\O3e=N8B#ρ>vV5'AᣈS'Z: +yU +5:K$,Ue+3 Gz8.%ɒ#>C lqDhQmSGG|}6P +|Tqpl$>"?6ࣣ%3#q.($7;爣e1|#y1#h6#8jDup4@QOBjAљ#(UFz8r > 0.nE|D~%G Z/?-ږݏm~g~@|aPB҄b_3l";=l!H4VTe*2Uz2XЩƗA (hD}pH">TQhG'ˏw* N,M. ]"H&&$,K( +J(\o1rXU)ճ#͘&Ɯ"GQ:QĿI J(N* +]|,ro}Adk֬eK +w#?:9"(sGD>VZM"p# GQ97ǰ{ʢ*3Nw*&9l>׽!ydϐձ6.FG{S5(VB3n)<;[6mdPWZ*b;ukt?M3>];ìۘ<|=ҡf1s('8Q! | +~v\A#1'fVmuZ>eHOx?dO׽WINwډ1Aa+NĞ#R` ф2QtxDѧ:X||lk\Xpsէ+h!qI!挥S6%cjǔSIx$|"ִB.]A*?j)zOr)DJ3Hټh9VNM>GjꚪU*rˋ;@?2v>O2aV?FM +5)U^yFx.Iya߀p|TjJ|ܛȩPiY̩Ti\:SJ=J+I!'I\4IO6 I_y"*;cbr+рE?J޷#kv?Ɍ$G&dyow>I,N$q7~AQh2Q>(DM[vfdAR·Hkߑ$>b;,x#?/5zqE;_,ZwYv׃GN1a?<|-|gSZa5J59s2r[ϗLuwm {= >B'MCݍkl +Am =(aw/>f~bD=dш ^Gg>\1lN̰GDmlAA>rKdr>tFemquD4G wy&M3lrOb>o (W[Ձ[e'xSҭѻ4u +F~D~!pt|@vI{|x~zG9a&,#qR*GGGrֺV<<8eݲA%*?(i(4?#DSMX(M~+;AGG|tqR]m|D~D~!Kec\ +)@~D~!+!!0ZMz,84GG|tVv+q|D~&dci!#q^`@1ɏ41%|WRR.,H;~ȏviThT+ 3U䑘'GB~G$ %-}w9D|$S/e##!#1Y,0|ܺO>B(i +::U}D (I,P`b,J>7<"? +4 @`PBApRQ{d{+WWE "]Kf/K,\P+_qoB[Ukc2'M{GlZ9A|t(b^{ʢ*3Nw*&i}ƌBd\ў>yr79mYQ#ch.*;1!#X3@VP'1L)9#Al>JruR)&*UOBn<XsRLn}o}n̘Ӵ|| r8/i_ti#ຄhӧCcayFaf%k)V.&pLܝ֋9 hrx";;;n'V([[kϟ#ϴ8D{# uri0(bgFyΈAd廎FD}lo?2`2|<}+#u_~|9cA'=%Wh[E3p%A >ڗ=Ξ.gh5jr&q>eHOx?dO׽WINwډ1Aa+M֑r?|% :V;vزereeedddOOJ-W&]mmW[Z\xteM#ϴql>]:)$![Cޜcצ{L 6z* 1^ĚVȥk#eŕi&xꎲU|tJu2߸c˿e[+wijt>,K 8p<ݰaC>}PdAt"$h%>vC_&{,oڴo߾rElPJgy'#(#}YooѣG>BtjYG|~>?y뭷$>Jj2vA>Κ5,xm$D#lG>6> ---=Afa#Qccj{{{]])#" GvA>B$)A8򱧧GTt ) Ro |]׮]kjjBA:{ % +G؉[[[GA?a޿~ċ/^|%A#{܈SsفЭ2q"ѯ[7_2 +hoj*aXH3OZd4@$h)>2S*'++:֭aas?{ʔ/Сw |!_ +9xvws#=w1q8f !B$Q>s>t +>_Π ?iFH'棊u*//g,U*>VOdoBkSmm + +G14]R B##A<|d3|4G)+--xG}{Aq* +8GC)w |G}:u% +]SXyBr9В|V0 [Džz{{qpb|˜ڤ˗ԙa rGX'awgz)Tq2yQμ{GG~ĉC|G.N+%!EGܽ#N?S6kZEJeR^>0,}ImsbOcG!>ɏv=uSj9DVv|a=mN,a߅ g_x|}ņ+׮5^[G3|RtggjsC# nzqewy'+~+bn +#[2Wx>߼9'V_ r.>dL3|T(ڵ󻣣Gs~ӔN']8yv0#)|<kߧ7/ĚyVehgNu(wBF,7^>ci6#M^01w Py4`LSXs6#כiqKg+p䣆}GQӫo<2 4sJ0lG>2 DGr Fg ? t֡]|Z+. zBO #͉%cHhh +&O}ժ _NYjN4? +]_1!Q|}Qc?˯3˗k!TZCG60Vm,7 r43WH9}xH~Կ G|j*{|9 [g_ٽ>̏qkkSsSm3Z>ݲo&-r#M/>as'n }às5 .N V5#=);Jy8oq玶^jى5Y ^uk▖ں ǴM+0 [ώ;ÑRZ5RвPbPWkϑH|M--M.Xd| +endobj +146 0 obj << +/Type /XObject +/Subtype /Image +/Width 1032 +/Height 746 +/BitsPerComponent 8 +/ColorSpace /DeviceRGB +/Length 47576 +/Filter /FlateDecode +>> +stream +xXIc}W>~{  +&` &` &` &` &` &` &` &` &` &` &` &`mZӵJ Kɧ2oJE]fcn#1)Nۑ`p-@ @ 1-Fo\p} |)G\-Zt2f (X.1D{ : +nWrevHV2f`T~bǦIT[_[SK]KE +YN4k8s#eWvdd{Bu26( +8j( +u +g +(_RGH쳍t@عt*qOPFP V\,;'.ܒI(WA<莹Le#eO2ZguP@I/] + Awv1ܯ¸"v +|u. u + !$%M(^?{7V\E, ?<n'盺ǐ\9WCh +=FD33ee }3@ +wuMRB[tm;W-yGt@(G75uI +=UjP @ }QO~?0l*!d=OJeXD BFFsFpot=+Mm:\6əY3퀸);-iO7se"[)y}Yyi|vч@~sREӊ>Cs+:7ӹB{ڂُinaԭ!@ L^p^Uűk9t Zx %U< +yDŽM$ık.%O#r{"J#yDSsf1r@ ľCjCG:Y, :omd݄Ͳy]ua֐kf>.XW:D/`eΥV~4XP6a[ĎUF + +P\a@ s!e{D 5RE(_l"Ifbl6[ӹ4jُ@ 27a'kx̳io^pINJ HGMI#Rıv~ŔB%o!y\ +Fz:Ys޲ОF #S¤*0{@ % Çʸu*azO0?&/-,2;;D. E/t0t?8ͳ4?/O'T邓ouk+E8P'~4#f3jB2j_?E u`$+.Lg@ y +;-QSS=z,NWyD@ .]/\J]0CYnIM:i5]_zQC_AC ]m%Xu. @ B%U_PnIDD֨1~;~kau0/Sw-ȩL,C~} *ϔm1g$' voyYDeߦWyTeQzN)[韱}šzkIgg>+hlP5=ۇr䴽kا$Ev+?Lr`;f 5y?,(T + +ELII,Q/ReGFj-|q@LoGӦ֞4 Tl' 1i+[<'3;baTm*{'xDfȟ`FeOQQbAj7{F-*)N0xQ\"_SO?dbPSEAEIMz-Fm.E)=U^RZ_[S!gjYiu8{e1g;fZY5-\_xKQs>N]et~x +1=-FF9?@Ŷј~}/PUU))~iDK~h&D$䵆5'7[g8U| 3USp%A}ۖ} ;![;.}=#6 m.i+mAЮn2ׯ< _԰ɜm]s-1g1bkoFIp ǞPu o^"nByr}]9tAnb8 0EΑ~Ӻ7-5 6ߔ-js@5x. +rOirY2fqD̦ kƷe2+;:cH-`-H-xSԺچΜvπBE SPZw*qɫ +ܴ?_>|]|s2j@ioosTm@rY +ތ=oAN5s[LSH DAHUHp!Ew=1MOl% +Ͼye߮zp ߴ5吮ii#cSl&)+V ]|(/Lqed;!Ⱦ]ް3?d^jhhO]@  y1/ IK HQ664Ӭ |2 p}ށW]CAt@f/.aSoo0SHa5ZuyXx|z|}Ó'|Y@,~AcJ[V9&}Tv*U^ +WT}hrZ=jbb ^8gI 7uo$z=i-jNVŔ_&즀] +>W /mn|UW\IqTЎ}nP/u_3|1sc>m=4]ՍaTL 97r(ǽ%DRCꏠ$S݅lj(Hy5" `0zd +pG&FLL,!qTmڦDL ~wgY٬\6{՜YЅW3*WЛ_i}MVob)nB <4d})mRL@H6SڭT̹ /jEVOگn>F<Uך1 a/aH/e):&EĮphr8v8FyHCJ0 QUP>.HCby122)eJ}eQgE!QuyhDiC3;cM/;ۘ + I)m^Һ$5]g@@ +QYYUU]U(8>>>66i}z{{ +23ˊӦ#'$&*nzOP.}1)oe +]3aTqu})(cmQT0iH,KWgCLźUe&e:UM-.lJ7(U ]=h^#F@r@Ig*zU(>b4{1+;{]eb4xA ӎeof/ +syY̙/VةνoիѸ.}#‰ul`jn'昩]lZ'uHwqNq4[h #^$lS^ y+`siE:zᷯ#r?:7>& 91459FjH*++#v_g6]tje;N1kpIe~hIbF'Lf鲾 >>^SS3&&}||\^3Z3:kEQZy'|_R _۹tԨO^Nm +ǔs-#IKEl4r*fPHkTД^sܼ&ٳ7߬Zd j[!$vBQܽ{kۻ/zE>75A֞.uCDTriPL~dRIb~Oxo[]Y+ޅ;wqSo."2 / ُ'~g$pMn)X="w`g{eSCqmu>x%1־2cj,N #eBA4lR6jw`^USUR@'P3 R_Vek_rnTy}leO 3di +D0?ى/G_R,ryN߹3{_U ~M{bgj{rˋ"vċ7"gH/ ރDi<)I!~>gva^`@uE\tZ 7;79Syn?. "vGcyt!b ]vݎ?V^XPPµlhEΑn|n][֭[/XdmЧnu:YUrGg߸B,r._CJ꒪7]] /2DLuwVqnB}!g`@`vo@V_foFԗ)u&4%;nxA[ +&/ 9b9΁4@G,RP ;#%1!86^xqI{d7JJ }=r-2^n놃_b-f}Aqq}GCkw>ugVHa(P̈́̈́,hzd/Uooo-$=p({ZYUQ+ۨ!f/Wmg>oPTw%jAT_=)ЇZpꇪ)oOJ;# 軾 +HO:~5@y +5 + +so9+y "sK!,ny]اMlR Vj5^vH(&Q=3&۔Ⱥ`)?z^ĕCye +ᓊ4~GHB1F/fo ^{ ml}z/* +'Ǒ' =Ez!?q +l 2 .P>%,Ŏ)V|r*ѺO3I5H(btU5?nP6ZAO_6Bl! UsYcJ:E۽{R<]]].ζfu}Mm_OP+w 926%lQ6 >t/ G.+vTW ރ.ӳյ .(hHL,-zϪ}@. +达=F~995/^DGexxpq +35 25 < fB}>!Se_rOM03 20@W?ʼu+Bbь:o~3[_j+.n˫MOLL,)@\67;!"<,&&ƍ_~%=Eo-,,LLL Ν;__!#XNQAQUEfdvI}~߮[e˖Q:Ыi ߳cr&7#vFN|I4WS?85OĎ. r +Fj&n3 <؂G   b| [,[/R`5?~n7ۏ?{w΁Gfix~Y8d\:%Ȱ(}sZ.'6kzYIg^x<> ??'=wvQ=V % + LfD5mǝE54 Kg䆥<2J +/^FGg%yxD>>5ݔ^n4ir"X +*zv\գqJ㽠/&U/^2~Soݷ A.&cOFJ}VK`^=3?y^#H2~e%cۃkyǞqq|hփOza'3~sɿGtv>.-Nг~v䅿:98z/}{ϳF Xn~g_^}yLō}ɒ%SnФ sqmLDg r:/mHw(n<,O1>6 G?>:xcӞ&eg |wZ䕧Y#=Y:cmkkzò}ϗ^{噇cnuʕ+;޸qc… G>wO{yٓ);Noy{B +!Bg ku:'^գθӺˇ>wy7 +yV6 +^ +~M[|^}8 +$$>Tۘk7?[ykңM|`ˋ{ ]vuܞ;3\?G/ +)!G/ +vOw7~նs~CS{Až^ +]>T[<G^vј8ꨓG>׍ й^ x +`d]z냣~jɮ +dWRxAWL= W/\},1Ӟ3}a^; >ӟ5``ˮ=<^ +PgDnɦ'HXz4xAabiOw8Aw,<^ + I anS^FczBh/ si]Fܳ|/p_w淯ӟnkg>w  +5H-9xAyx-2֧ZG{T[<G^vј8ꨓG>׍ йφ^ P9Oy P>z \Î%$'Kcz^P=oc_~ワgufS4y9}T#[Ѻ^>`Bɏ|U0!/ +(')H=Q +5/%k|䯿$b)J8H> xApph >݈#W 6Fi;蓃 zTO7hDj1Iޢ{SNj^ + 2R :Zղݦ58f3vR_ۙ68>`!@%TP + +PH}pDl^FH^Pj^pn˃uq}W;w 5d]C + +@ +tIF p2JAžԀ DC + +5h0T +MP3Ҽ %]y/ +0 ۠C@^ + 2Aʾd / + +9=OE &w4XbG,'m@F14MUa"+G?a7KZ#^ x @ +Q +|cT& +j^ +ʬ^P0/HшR}%^ x/ + +\YF +/^ +Se3)/ +OC/(L{%S^ x @ +݉ |} g@a W 4^ +LgFD%A}?KZ~UDŽ,^ +RC>g~?_t(ݖK^ +x/ +) i5x rM ^a anPbœW)H t= +Tt8W^Q䣗/ +jM\uA/ȾdP/VjJ^ xx/ +^ +b>oZZk/ +O۽+A^/%a#Y9 +7A} +dn.RxAD/ + + __4/ +3^ x +x/ + ^ +rh +t=o +75|^wu! +'xb= +,)Rfvr! xA _mٲU +\)HX# v[hp^͛o~+_Y~( + + +D;~׿͟_->~SO=/Yu +(7/8> ^{EWƌ nNU +m O{=;tԿI}}UV/G +^zƍ + + + +r}]؂I͢UY{{`U +wt cVț*+D\`#""JKK[oijW;M. +D +A;.W~#QGRߙ_(V؊;o>HA4\p r +x7s=Ht\PbiNp[¾~m}EOVKKd26k, +š?i +_#o 9^PŁunѱlE_߱dj)BshH \ +Qxd2 +l0j5 +1hU{FY,:ClhBB> 7- +ڠ}bV@{,X@݁{欔Yvz%% efWR^ 9cz>?lڴiS7OI|74 +a?;RE|cQ}C?!Ecco_cW9>`Bp%?c'WG_ræ _&|,TI] q4ƨ8^[FKYhoS ?ۮoOتk64Fendstream +endobj +147 0 obj << +/Type /XObject +/Subtype /Image +/Width 1032 +/Height 746 +/BitsPerComponent 8 +/ColorSpace /DeviceRGB +/Length 66456 +/Filter /FlateDecode +>> +stream +xXךv/i7^KM^PQTT@iR"EH 7Hw,.,; 74e&7g߳avsfsfH$4 MhBЄ&4 MhBЄ&4 MhBЄ&4 MhBЄ&4 MhBЄ&4 MhBЄ7ӌKM$zP??>`-3`m4fGð@ wո&>@f͖fCn<_0R]` +o40pw +p_0S`t+iw3@/v.6M + +򹫍 oӨz` sZ 4OAɥBmvƦ77& ( + rS׏|'uGC|UC[|>28f>^`-ILrוjyeCeԅCȤi[ +%sTW21lLB0jPDk#/љ]=3ZVz/O>9k[݇:P6 +>scGw&wc>hS}fi?y0L@ v.8V֢kӤՠPRoC_D_@_|w999=s(_G4F%+) c- +bx= `%bDcg4;Z* +ml_ֻc"[T;.3=xgskϭ# ;HH F͗͟2{R`_űVH͇~j;*h781fHaL`?\Klî`oTh l7Ll_@ښAڝ=m@103UN?Zw +/l,fUpV&|0}{G2}ϛey + ֦@ 7@ |@ @ +e +(rQskP\]SK.a>G +a&۵TAnU\ZmiESJfytBAD\AvA-Z]WU]Q]יQ'b^^٥0r`D. +Fg,(ߩ[Ox\_:Fwʔ\'w= +h0G{3gGgV.OT +`^}v5ߝwvkCwf3ҷ$%>& ++uv + +M/ 1y_i?[gola?E_ + 6ܡ^̀}*/_ݛ' jd?{[ +MN.kO((z /w}Xd+ \W,oij{kjņƖUVV4ԥeW7e7d=^3mE&z?g` ]=7NFЏ8|?65mhU&gm4La~y_op뮴nIM%v/2,k]Z&;OVIs4T]+z ԃzQSºU'1P(NcS EEu +0;;;339>6RF=tZi-B=w}ND X]3 -J)oNP>(Ts`x1 DzQdGfmcoUTdQNDrgƄj,!Y{ǜFplWMb/' +ǂ>r?t[U˕EA%MFm',n*ƭY[v<{T2=vRѵJ<8r?pmb{T-Nw<==*VQS;os18I\(cp}y_%BU7.,VRRW?ZO.ƖrM +-BWDTFT=Ϯ:T + 5MuR4)^z1:ҷ, HMh.;]F\Iba^h7;y)ȆUX"e%ٻj榺0;-~GH-O|AUEa p_@_o@g/h0򳔶fA',KfVt3LLSghW= rsܘ +`[U91.lW}|6\666U&>h dFY\m^Us +jOcSKCb s#C3S%;z%=<<@{nٱg7e&"JXǓu8eCQ5FJ4.^yEFx#zJR|yO]ϝ2|Q +9þډ;65Owko +8a$C+H*|KLq~ u J+(@Qk_`$8"',#( a^>L:P疦MW.[~>q@Ai5lbmG\fei%VYX$:~w%yZᯒInWݧN.3O_c Y[ۙ@Z1_XT\TH` NeZ[[2"FHͩ/&!+}肈8y2Dk +0Ke|fAvFB3S]5yy9IQ%_|{;nC -vjUW 6~k`E[@HC9 ֠, _eBs(D4q!.Y@Oݒ. +ށ%ٜ^E{χ`a5`tU>2»`=8}^YU{̫LYLfJ SȟfaL8IZ7/(13LA*v<]tfk7YLaTc`2Q CmԆ.uvpU-UM\GAѹ^!Q9~tWdaVZ=Ut]nsX\ފO +?/|fVQPV8V_ 7K,D$oxⶢ:{^F\XU]Zr@ŢXm^ +VF++/ciDzY׼Xǭ_/id2ϴtޖURRQ;;M} g +%\(v<er[eQ@w'65+oA_ x0/\6 +dl}|/Νm{6[9{oG%^|ˑ;_ +K +B>a>4g2fq9wU]FdgG@ßm-e +X +<}j7%_` %WɥDӳ}Z5}Őb2][KiKs⥎gO'SAhjQ=7֞:}L.Y1{x +;}AӓEhi@"c+>l_EdWQ9-&g%t"ؔ嬦SRGe Ó=""rt-?+Otfy'|іG"g]$yXLedV|êͷ8`65Q_DK3>/P# )ɭIMnjžr)34yEhg0X< +4V)F!'(sR9q>\r#ץN\/{p5~]gOqCisQ?9ٷIBH xhl0lAn"P ݉9/c:`~jlaǝ/ޖ?(#-$: ,K2gãv +isΞ$U]]]SSS__y `1pJee3)3~PP[yjfbr\!cnY)n/M'8ϝ;7f5-[{"( +F *hGGQ~gpޜM: |շM͸y<؏=c"S&]>W޹PY³DRΛĝ4دսP7ʋp]QN]<Н|O:?4t9T̿n,CiF>S"rF6.j^5wܫ;Vd;{I>N/*T`ì(%B*١ym%e:jEeoH%|tM4П5z>Jfp =C.Tн [밡cJq}態Oi\qҼ /uCVTB +쀶2ջ"3e}vt\6@kކcp{Gr'O5 vR _%w +hf +PxҩGU Ÿ_n + b<~/R>~V#8"O}·/^iW>TR]ƶ}Օ(-||ٞ>x,1ULNmp]v!gŭ3ʢʚ}csMw +x/ p}?.Oۂ~S_#w`)t{ 1Mf$eξ:zyw" e = W6l6KR_aD|AЖ(Pb(x1=73?ep4-~#zq T607::0""788/+59.:,Σ~ss?-BsrX-/BOs{=3CC/==7mm}kEkr(En>V.ʊc k &asw%MqIr־~C ~n?;H:!|/coL"lrGy^j}0XmRt"8l +PBp\ ՓkRro)3ܖIf ݨ.֦:Wq4Wn4Wz0Y<,FrN9Ze]J"W_쁥pe!C[v љaaAA<՗'Y\0ˠek> tqq"]fZ__0g.Nֳn@V{(EF 4_ʼn7W(ОS: xb2'' +t'(S&w`OPfu_v}n{QO;|VN~lF,-r슢5yWay=Aei۽\Bٰqj@ p(#0z4wYzi5g?^?nmw4|Qc`[oQ5傯O^bJy%V9U#S uGucsPt,(F@wi^ڲ}Ǻ ر۶KݖUgp^_ً;Kf/t'G30OԦGb'xR)kJJ\!tuQQ1 qI[ +Jʪ{9?b,tQLQ +Jq։꫍YxȹgI.h-E?r2b6׋__05=49kp8pNlsaw3{˓ >VwwwkkkCCCUUUYYYAAA^^KOgb6w3QM.Q](?N~۹_0t$3xUMn}/gs`F/ѝ6&eMҎd+]syZQ~ьo%@\0kdB268IFKy1hMuJb蓐(ܪ/(lF>K~ь.wA^<=pn8_W_o/'/OGoO'o'=ށ[|_ )kOG?Z?p<= :0_ybq\ t=wdB)yc&Ƕ7F/ȥy~Yj F9f&Vv8uu-ߏ +<ʯ-;Z[ra{Ir.+Q<=ßOZd.G{8ua捺 ~Rv/DJ?,b{s~@k#Mc O +]3Gs\jM$doɯcʹr#8udd[#|U{J-猇$]'{]bT|ḞoƋvWʼn&nzn +_"|a݅|"˙NrurQD.[rw}}<IMOlhAfo+9w}!;#vGs"ZqDEoNE?=8!<#~&z*f2ٵ# X%V+TNuOԳfoP! Y1I)Y5]=K3[+Y3[-KZVuHDY*M -+ޝSaQ3Ӯ$jg]ihQ?'\vo +;B2>=!~gǔ}dFIஇ~CVE}5vNWzUfn5_ "ԴNO!O5wO$Ȼn]sB0:(=s3{Fy9q vꪱ+g;g+K:̼ xuXɴ4440׫_/FQHGRD)W|(\('l(M);(U)?R|&DYyPb]P 5\ +.ps %6}Ճ}dq9qqE(k\k\ \.MFThxV_S*chnFYft.HC2| +4.Kl}Sb+*&*''/^q}UPj4?O,4(Ҵec +HH:'#vKG8⓯[+/+V(y{drIEI8%$@MMM۶m?DDD'~!r^S 2LJ"㺧? +J`LEEPG49Kr(S.$_YQja_zˢqvbh15<XٝW134D4 H[cx7d㮇; 5;IQ]Qsm;=Z1Լn`(αnB-()b:-)Ji;燦46a\ܠp/~9]3@K~}-Fʃ:JT%RB (P<3(.)M,"bJ15=33*` rݩDPXTTX쑈%Ey5sg=$y3Z6ႊ7{P|Q_}ZUk5~6v}W6૓rkE7*!18",Jh~3jwa/ +]r}&`n̘IBN__^__קXǜrB]EsFrF4uOLϐctKN9|dJQOg{Qr*{G'PRB&'&9u[kpN3QIS_p>\#q;?>3Ey`̇F7 0=!+++7'77SS*/x&x$z$䡏4(JNA8PRROsZJ*x"Ta5.ø +)<%t<|9@`7?ޅZ@S86JlB?k~*nDvz=ؙ&9GYm8F=%S8q h_1lJI{:m%:9{&uƱ|ư"BܠaP5-7$8w.HJKYb׍m&HY+Mp'W=)Vzķ哂 I(GW䯸1Fk?5GpCrN'GU|r՘E[F7݋imEil4 5 *U+xf,\}~ɼ1%9D=yuuu + +h zcɽC0;Mn QK&WdrkiEI`/ +\ jk]:@h$8WENt ŏҾ>|__I_v7z,qAwg{.{ +TI4yNTk +JوӍ`vL&'s<{ 6KmIS.Od陓ߌ[s#kQ{Y~tϬ'Ba4j͠l#hu +-W.hk"Lq5.J,N.APRC VKkKGn!R}R*}R +{|J*Jr5. ֌Ko¥7F|R +ߔR"KAG_F_FF_F+Z7'A)1rAO{)ځ5}ÝS2NuByij;GY"7%}C%5MEO Idl\]ck7 UA&ne?Q~:̪/ ͍>(L*3gFy&pR_P[sNK^\h oQYZe.SbP@QNMe2;F9 opsGO{`N9u5[{6O_.JE*zgDEHH}y. +I2+DW+D=aC&gR $q,Ͽ/p^aES[Gd]L-4uD%X9[!bƸC'.q ..N[[;&&}}};^G!%uZ`>Wy.41,pn$};bFgg#a*im7O?Qjm~ QX/R(l$ؔ<FgFFF +G*GF z@ٌ; \]\yXDj +ĝ7,[Y5kfe$M.MʙS`oW_{pgXW=VP@$NaP0:JYӜeX}ts# +e +2;3;33Zh@E*or \`img"r̎ +VeWeHVq>0r﹣nOa=L n}AVE5Vz~m`2ʣ-#a}#YٟE.];rL7:ңlBJ:EjFOɎ12A\pC2rT$4U-Mbߝ#*jgpf3kM/ZïmRjw6k}xv& +|V?nXxͦ/@`Z|\/ZgL5׹ +\J'4. Ϸ,.wuoNCt@傌JRΟ|55%(nEhTu\Z(˧@EM~^y\CkIUxRzÞ&td  [m3BLL kX'~^e5,(q O^Y,qu +mZmWkk_\ ;~ +̨L HOc ,#mT.- \$Q 3Zi\VK-c Zq ).^!9a911Uâ33j "22s:8[(ˡ#[YVcY&+d5* 0UY#(KhG9lHeO?P .sr2 8Gu.``*Q +/˥BO |// ZWL4O#4'gww|__:sB+~v'G/8STVE\ 󏌍+(YYGOiHMN~ xݳrϋ蟸xxsJ)wrJH bh PF <Ҍ+#}W+[+7TIUMRJTd}]MG{k[ks{{= 6Q5OLbMJ򦗤tϜݫj]8-{{$ѣQ_|wɘ" =O4_9+DxN&r>nq&p}\>ݯHjKb̓;.Pdisvv)KV"yR%rJ>*5 +UbNB6RlS Fa{J]xoV L".!vZ& n6cƥcIyJ6 +|u.HIFuͽW.uڞ ޏ:tK6˵4>Dt#4h\Aޥ742Z}D*a#5RM$4978qc,IFἾ#0$@ TUUyy:.DF4oyfzzbt.`hwܙR&g=`pQO ,sApYhʼQ_B +`rR9Nՠ64$ ڎuDM.A*:,rN2[Nq6iުlssаΎe^!gS_ko1ȩŸrLٹȒW .8w֗X*kG1+hPHeͭ7~{W0rY$3r,(=O2O$ѣ̝E`.;lowH4&$k<<\k1zݍz^ri9A阠4Sk Һg 6YZQcXEM]ZF\U]9#`9L_h2~ EEQh!3N4LH3pDf(8;ZXܫY_),~hp`,.'KqbTw1-AAGP}}\âTL *FF!cP`k~? QPK!z“Mb3K qEq111weUt6WeX?Oa +`q,-'7gs17`gC^e_~_ߦoc +{1cnAO%ZPsE/`fe&qC9X~O#nxn=;404?Ht)S)*TRr S$hT54nIɄ] +pH891^;Gk6{]ZZIeOjU{.qmޫ*qJ3K~C.(e>n+UTlo./P@ +||_ "ދzQm6 +C($ 60ϻ/pJ :9TziCDz'׳ޮJ5]rr.U4Mbijhe +|fg(jIɥ#iîU<=G,QQQw7Ǯvj ?[YYYww=K#k:П3a##woc]>3QZ㈰cPf``  RWSUmkd P(i=?2*e49shqp2Z .@bFC|4сud iQ9fEa r T-2]||R|3# J["-SK+:K+ +J:tK(˭ᾴl>}՚7?W_kӴK{b +F +螶WkGEuϋV9,Shuӿ {Ԟc*.u^-P;R8u1LJsH>$\q4䐼O($u=¸ +{E͍z%JRB@~N<׹ +k +gpq_Xw䅲}&=2# o&e}e#6Z jjk'&G8*{Y_p(ՔO)~HZ뤺96t9uTZkՑԼ*~m-mFR~At"yfSQۓ:3ut'8Od +CtpN,b^1u"2lR򧯶 +ju +1"@ 4D~JқpyǍP1FM'+Lr"DN>D¢LmZ;~c;>`> +d_wj/Bq۟y貀(.V)uh1 2 e4-B+&:aqpRGp7jhh(++.[q@Cm^zj#DEE!.8}dG+S%''H i|lbd0p|hptw{s}AA9=.@F9IpӐ * Pru uf%..Q1 ~n?V-j[ۇC]HΨkIJot֌4+z~,SG5.c_(Y5[h4X\ ӂ>ݿJ,TE1n讹]wgk''S s@߀@mpq*膦nы[E/ꝓ{-OZk؉X8\7j$ epZJڕ㰃o˘E8&P ;-2"o}MޘC(g#\/s~~͏*)ƪ_Go޼2`}L`L;柟3zkLcQƏ~>_d> +KÁo&eכPn]&/)U-:XrAJŹ,25$_X,7*1=B.NίՕvvh ::ڋyGVV`SdwQA"\fy˪3&' K;O>cԝIDC0j_8B~c WUSCy>KFaHܒFy>KFa~iB2>\sD9IkJ~%]Ý](ik/)l*4NUO읓DYn/f* ` ^<16cjfalbhkc~yˬ ͚392Mz*bWlohf:Hl+ j 0.x_Jg?,(xх_W^ye eaw㗑z[3ĔpLG>ѵo~o`N7l,q2d(]]V,2Hɮv|^B +g4er"3+-MŜTA2_PIίNYquXዸ $-=5qD`V&ii'uݎV;.(()oEy{C@YewV~K`xQisޗb(˶9s+,!s :|'p.ݥl>9m{VS CɿeU >C*7 Oʚ\4߻Ͻgr޽{_}/7?kiڴiSxx8\m (c aYu5Ӆ;bU;iNAR=ҞΊƆܢȤܠq)Yx|;l7JmMaJf\DcܶFSs~G{P +t|z*;*jrRxzfE,#DܱLqw.P5v]z3^wfVɭuE΄=×]{'ʲ/ +&,Nʑfps.x +,`p.x& + @  + @ yx  +f)'    yD`0p@ 6 O0 +s +heHe2 \JEP}&]]CChR.-B+5ݣ~a0ph9p O vso9J;v.FTt˱Pʡ@!O!Wm6$ @ p,f9+pC9csKk;^ޭls+[>^{H^]C.ZS>}a0p@ ^} 1D +jԩS@?kuT#-)RRek*S^";g^ +$uv$[EHQ]]/H +4 L)ɖ7e G_Tl5m&VNXݤʭǦ4po뵼n˻u.tq1qޝ +Q +T M +"_Qrw/_ +m + +G^@ + +" +̖!JZ) +jaxٹCs+ϰUx  +xE)/ʂx~A u6sʆ6gg3|۟.*qky}Ǐwk:O,}j + O;o*^K>oÝ?m^ .⪬/|QԆO禖ANoc> W ++~ qru!iRH};  +; ʷS̈́*m'>ztf՞^H/Q~&F +o{k[z=QfǯDϣ~#"7^MAnx^ z|IU&Tm9+=0􈮾7rl}Gw x^@ĵSFR QU5 iTؔmqwwmywR .&[ #ĵ]7j?Y^֮ɼY/Mu>rtSvY2zgP[*5^ +)?z)طiaTVi  &x +ˏ /_ +  +o>^@x +8ʪDo/ x^ +ܢ˥caz\AsǪrv) 2xx8xA^{]J~#ep} e~oU^ +_^`!/ +$, +NY`w̶^@ +K"u?S +_1D +Ŀx@z״w + }b/=C/\Mּ~[ +ߩ 8/ + +#zW"Rܞlm/ +y^`  +̖+N Dr̽~  +ih fR/ +N|#s 0 +Wå mP,|n0?%)*bA,4P0'bDx +i +|mbxA@7+9-FQcx +U*`0NNwDd6i B)^ +$k&vG0/ " +EI"SI +nvLADx +3U-Xy/,L*x +?ŕH-@|#;   +Ve2r2op +T\T fӍD +E9|/ +lD[Ƙ5 w + /F +B +y_׺uVR%==}РAe˖ + + + + + B^s?] +{E;M` +{Ǎp-_|߾ǏS + +JSzhC'¿,ѓ'/%^9D.^'ŗJ/v 29S%s Bė%-<_Xoɷ +/~bJO/=q_uN.s"ĩЗ'N(`NRP'/{BgSߋ)8U‚sJN/Ntɿa)</K/Sh%9s`΄xs‰/vӥ矾Sg9a9{[gϔ3%9sT9 }p`ϡC?z4/?~SOF̩S'8rhl !|TQ]EK ?_ּ MūގYmiGͨL + + +Ny5[<, y + +;|wx!B\;NT͊LI#W.h7?A/0S +%^`w NRXx\$x%^a ^@!ϼ@C`ϴGH*yApߦك]xM/䶥`@[P ͤ@DH3ys[bWE/(;ttҝ;wVAA7xЁoK/ B^ *Ҋ'vyp/H*IAnLGAV8 ?(4R "3@$<կnf:A/eK.˛| նm;/2/~B!ěccXE5eQ@}~ /ջ9{I@T-襠{R z? qOcg%--%xWq]*/ B7In9ϓ~^ٸ@)poSyZT?Ȣ +F +Ν;/B)fpȑ6_CG y9qhP B^urB!/vz/@s>!Zڷ3EZqe>fD/0\A.M">X35ii|pxDaEG~8sTOw~)'N <}p7*[6 !(vj~DUj6߇h; Q镝uzh,j%IR .o \G1k:'vo^x9*_fv`;R/_@!䩭Ģ#~ԙQ-ٸֲ_۱yoSrEC)P<$T\c?^/NV?t00?H6 cۿ'B!?Dqm _./C4ljot'}\Tc޻N5#BS: / _%b: ?hG|s[:[a=C{}^@!p?1G8x !B|TxZAy>O$$ #}|_0B!?l\K8Lg7konϾ۵oq0[vnjۺY~*cB!l\K8o]wĤ[7,dˆe[/:{֣M_Ѯo !BƵl!B6pTz7bnO9eVXf_79NK!ϼ=N;r +²e.Y,7[T싲Ek({ZسǢY z,Ce%enw-9g^f槙 J_~sav4LotW?{廉J/(UyEZmb^h>{3ѢDޝ[zkj77^k^{eR c^xnL?=Qk`G> +stream +x |eV +~!j]* @(UET>?Pw]XwtURRAzR,g6=r$M{޼4L)mMLw&'o&ϞvH&J&q&/L+\*Tǀ2 +221Y*-Saf+ax9"# uZK&HƖ`x.6}v~C>!0 6 +ϞEZ?JrGwE + +20? UX%AI, ,P,(WOg 2 sʴ"Bx!'G~~Nt9*8 vocpi%Dai@P;) n砒3%WlA_&F&ku8fDڭ3=!c:,[W\e,\a5{/0p' +Y yP^"/y +x0m@aX߼1ҧ=U '§0͒n?W8:X3[pg7<L+d/|~fǿ4|a԰c ?0Dolo170Vh[2`EHiO,bk|;T7S=l뒲*ߘ|#Rab yL,Ҩ>tb +׳ݚ_r;ܻ̻{6ƍNߐ xC.GB$Eʘ#|8V7޴M_5 h߸# vfN};ޱ ^e[Bs`o?vV|IʶG+,헽@qrlꩲJ*=8sB,Hlt>gl )tɪtdUy\6 dyl'Aq;Y5[1c\ؚZ׽PY[I?d~ J::_~ؾzʿlUZSͯ(_TX=]j,z,z^2wHጧ2';L7g2 pԂ Y72oGuRO3Hˉϸ~,-̒J&'A\:_GjtlצY*4dZ_~aJ4V|Fgo WΓɢJ_[:dɜ!%rO#L5!=\;O?h&X׳$;;Lb lv -r%W;]] -EAeZwѾe+_]Z{f +Ҋ1l UkcVȑKqk oY5Ke˘'i/98_^9A5V$ϕjS$C3NZc8rأ:wէz0KაE`wv{I璕A/\A -}J7^Ϲc)d +M㯥)MP;AZI? &S"#wuwFm`B0W~ak,aE3i-,>鴽eǖm&]:[+7Frz/]0t^hɬg?Oqx)|;Yȳ9ߒsΟ"s>}y~?sHYc> YAcގlg>r&9 9/0C|P}8ܪlǦҔ:?ƨ s3L0-v̐5S1ߓd2%%LRhJhuF+7E)ZHQLU2wH.`qXHaW'=zm:ǵËI5&Yo ί 7v42ꬁLpvr=ö@x ߥЉqKtG/8d_} +:}:b[5t~Qp4!B6 P%М3m-qnF7pXsaix$iFYD +V<;j/fV8)!;o3*7}Jƿі>2}EXpvX|oLt !5W5ⱌ ?8F?e;;s>%KKj +FߦS]F "8pBBǿLƆog'og'ncme3 k+uul5<7l4ܷu2)}N ) -/_9Y*Q/(S.ln,>央 }wڜ@9~r=b W̭vI3R +BS#Ҫ i= +ϭчgB׳C70Ͻ0Fr5}k 1.!J8MjnNsIҕ?쏅C,\idhȡd)W )ޫ,>HWړLR}pKJO:ֱMA+MWrKcq4^vr3w +@GT]sVj\}b߫ڐWuV[\}(]jIXdH/ 9=RߛS\ḑ +#X4~BJ{&'E6;>gIlΧӾ[OOm +^ic;9_H+nt+=9U,R_.[ȼ‡=Y;gde]#e]#8-w'|mu [M;]Oa{1 N2.cO~̙rsF_K__ =krBZN咿3K0Uҏcw?~? w{S\฾AzXr`XiK>Ӹq~!|!_o3?e m&QA|E%fPBbdgnMHÒ^H%-c9KE~ ZAamlx +r~Ot>:ZyϡJ?mY[Ve1100ڹ! ۑWu[6k/ BEg8o?.W݊ysĵ[9mwVIrr· 竬mtPrۏtUu^=v|^;vx*4"B-ZjukaOe1X q*4 xۜߔc6;߫kv:_Ȼڜu,ƧOfMx29δtv/D\_Ɂ?_$ӹu(q1/g~Bx]Och 95RW}|CFQ,N.>q>Y] mQ}idLo~ꃗ5,GHB>pYݲozs+w +p=$zYvД<_t~ӝ\R!t׫iIߎoM)[ӓd} M~kzu.L}ѷ1 A;8_*=_ la-NW U:_%??_s_Sz\ +GZYIN{ ׫3/$كѷUN.:ŝOwS 9N緭O 8 ل¿:p{{:+S?~VtU +qyv:Gc``vo' +bi|ApS~~TTþƾyw:FP+rL r>O^#q> ~Qy y""|Kjn-|61||TSB"o c=yn]+dz/o +2|~N%MI9bbt|mfGs.7 F{_ 4×Z +8+Ep +fvb9WZర֖=t>)lR%9$.QR 8} gTz~;&<.9FdϱW=D 5'-3hjmC,nni:MZ'KyiV+Zz{5UKd%:vp?e?.5u&~rC~ekUC+qǒs*;' ̞ϲ\fBvyO]I3SRLGzQd#6%-/^vZeOl?^`Ϥ ٍ,G3,RslH|p>dgS6jϦB54-Ft))չLrM>p: Od$0&B2l}m0M^+^| SjRWV %_֋D12zF[c%$mBD8 |`4@+|<0029eVt~h5jri(:A3Tڦ`uGdqodF]Rsc``_AĐ-:s>gHрfZNW]],100\ol%{:' )вJ) +R100vίt*X캜 ٜKnhUr&^ ^7<P&"R4e* +B(G㡁VB^EMΩ5p6x:p8'3 G/3yjAUMeSqW3d KMr, +_E{ +*`NY<} >d +endobj +149 0 obj << +/Type /XObject +/Subtype /Image +/Width 225 +/Height 34 +/BitsPerComponent 8 +/ColorSpace /DeviceRGB +/Length 1581 +/Filter /FlateDecode +>> +stream +x}LVUFE.`Q2WJh"nmD8F pMeTd<"KbB\'б?a?ͳ{=sݳss~{?*ܟ=M(\Q..(Ge^_[=>:¯ΈJd,'+\#*R=*0>.\s\#w^}\\%yEC.\`goqGhëĹ'yH~;4>qE o\ZJ m%xvQC.Jm3P/[ia++D|58soK.ESPJXEkDg:}rQ (:ƒ_ٿr +6FDWǯ7y&TV'~.57pͅXz._(iT Q-pQs-$ +ƒn&G[N4]TG,x΁PKQ4tC!(L\#v.z+$#X3&Igӽbn/gxOJ;#7t:[P*TNPyepQm@EdnKLn&(E~?ěPY)]){s3=D*EX, +\x𤋎ĊYd/E'F4 +2t@Ԇ WbJe!Q.v{w]k<T:`y^vٝ[l D.WƓKf3ä)%3̾2Y8>h^}}v Rl't_Y(t~Z}wμjՕ?)(QEx +[\b 0bZb` eSV2٩buDQT " +endobj +155 0 obj << +/Type /Annot +/Border[0 0 1]/H/I/C[1 0 0] +/Rect [177.551 525.0772 189.5061 537.087] +/Subtype /Link +/A << /S /GoTo /D (figure.1.11) >> +>> endobj +157 0 obj << +/Type /Annot +/Border[0 0 1]/H/I/C[1 0 0] +/Rect [238.9406 298.57 250.8958 310.5798] +/Subtype /Link +/A << /S /GoTo /D (figure.1.12) >> +>> endobj +159 0 obj << +/Type /Annot +/Border[0 0 1]/H/I/C[1 0 0] +/Rect [72.9546 48.9074 84.9097 60.6133] +/Subtype /Link +/A << /S /GoTo /D (figure.1.13) >> +>> endobj +161 0 obj << +/Type /Annot +/Border[0 0 1]/H/I/C[1 0 0] +/Rect [445.5145 521.7192 457.4696 533.729] +/Subtype /Link +/A << /S /GoTo /D (figure.1.14) >> +>> endobj +163 0 obj << +/Type /Annot +/Border[0 0 1]/H/I/C[1 0 0] +/Rect [412.4386 269.308 424.3938 281.3178] +/Subtype /Link +/A << /S /GoTo /D (figure.1.15) >> +>> endobj +165 0 obj << +/Type /Annot +/Border[0 0 1]/H/I/C[1 0 0] +/Rect [459.4226 141.383 471.3778 153.3929] +/Subtype /Link +/A << /S /GoTo /D (figure.1.16) >> +>> endobj +167 0 obj << +/Type /Annot +/Border[0 0 1]/H/I/C[1 0 0] +/Rect [351.6676 48.9074 363.6227 60.6133] +/Subtype /Link +/A << /S /GoTo /D (figure.1.17) >> +>> endobj +154 0 obj << +/D [152 0 R /XYZ 54.9921 757.9346 null] +>> endobj +144 0 obj << +/D [152 0 R /XYZ 193.7558 582.3632 null] +>> endobj +156 0 obj << +/D [152 0 R /XYZ 193.7558 355.856 null] +>> endobj +158 0 obj << +/D [152 0 R /XYZ 193.7558 118.1486 null] +>> endobj +160 0 obj << +/D [152 0 R /XYZ 444.6633 573.868 null] +>> endobj +162 0 obj << +/D [152 0 R /XYZ 444.6633 361.0951 null] +>> endobj +14 0 obj << +/D [152 0 R /XYZ 305.8997 295.7337 null] +>> endobj +164 0 obj << +/D [152 0 R /XYZ 444.6633 193.5319 null] +>> endobj +166 0 obj << +/D [152 0 R /XYZ 444.6633 101.0562 null] +>> endobj +151 0 obj << +/Font << /F44 102 0 R /F45 105 0 R >> +/XObject << /Im11 127 0 R /Im12 128 0 R /Im13 145 0 R /Im14 146 0 R /Im15 147 0 R /Im16 148 0 R /Im17 149 0 R >> +/ProcSet [ /PDF /Text /ImageC ] +>> endobj +178 0 obj << +/Length 1303 +/Filter /FlateDecode +>> +stream +xڥWK6W(5᛽IYE5zIBkkmaeɱP$mٲ`H7BBD +b-DKM,*F4Y#<4r^"*A"d:v2[zSdvuzDj + \,]rɮtDx !@wqp]FG7;(*-s|aՋs/¦-j\`CbEړ2իU U$-wacV!zљ,:<˜-zF"Ux' u\E+3? +HCd~YipUcIz3a82]_^]POkEۦ|TsyQFendstream +endobj +177 0 obj << +/Type /Page +/Contents 178 0 R +/Resources 176 0 R +/MediaBox [0 0 595.2756 841.8898] +/Parent 120 0 R +/Annots [ 181 0 R 183 0 R 185 0 R 187 0 R 189 0 R 190 0 R ] +>> endobj +150 0 obj << +/Type /XObject +/Subtype /Image +/Width 1024 +/Height 768 +/BitsPerComponent 8 +/ColorSpace /DeviceRGB +/Length 394860 +/Filter /FlateDecode +>> +stream +xXGO_1nbEJQDRA,tTzQHːrpd=gv7SDv0r1 ްXΆ` Q`0 pT#&Gtxsp$X6E?Y\ + +>ߞdIB9R`0 q +nɫ ۠.З&]::;%-{3g2t~m&5JŶ+? +uz2ڒUU* VWIl*^sOL~' WV/^/w$~ +2h(u `0?0× wt<;wYu]8pOKS̳mr2*n^dOٕZ`/uLi=/ZsVF +@_Y 2BYGyed?c`&ձHux7a3rݡOA`z&?#9,\ZF֪+?SqpqEYY`?)rWشiN() | ?EiӘEIhmվ +Ik?״l-5rPi/aM1R%7ru6 .BTHR0ogmaky5_X5 `0銶::^beoSoP-SXtَ_diʶ-F>z&0, Ah0Rl0=]/G? ۪o +A|^ +^f\( LпX;| &FtAbPض^- +vC{\|w!{9$ʪ',7r6qn1k +[,LwB¼<[޺JvΡ`0[i2o5agզTo:G[{BSei i?H~-mIj& 9L&:=b؏d(UbDV^JwP} ~LVQ9Ȼ')aѓ K>#3 p56ڽD<}& ָւ +3﬋F-N̦D&lMc!\#j,\Qb$(#t1;br5Q2lPvSW3W5J3sº 5Cא{XCkyOBJ?`0 fŒ!nN 9j8p[<} +m2Ŏ_NҾ}'E'MQGw?ɰ(H ^8Q)NQ<@.^yl+bw?^|;{ށ,B9I`I䏾#يnI &yŔ- (dD~Ai"֟tic "s63'v6w1OH!ix@²tP=L!'A1,_yBlC[вDlKCyH5(!iU+E3Eєn \,K ]eQhf -/Y'~ Q'(d XVx{y*j_LyR6ue6`06ูFC؈.b1!.ݯG߭Mv}}\qg;}L!̈́L 1L&*^YVPY3S~W-0Z`v9;//S6V#O}(.*Q4&uV+:҆n1]ʹ>r=ߛg<Ô݅taYp<;|O:{ȷsEbV)˘3Q֙DӁQO8΍>tKZYOsG˭];[SPǩ"& ia涗,9R̋,[?wݾhz0zyۑv3v4dkr`0 3hB t]v-:&}_T2aTTϔ)F=r`01gBQ0L& +_oM[:4 +@6:s]GV?f^vإEZeUG)Nn1icٲᲆ1 7牷=۱][>ߪ1c4sE$1^ّT}eː8tC30!#9Ҳi<|i ,,Ni@36|FBz=4P%P:Ik|r*4)1E>Cg)7fF)Я]KD"k;04}Z>B,B};k s"#e2tQ0+ysCM!vwi 3$`x7?j'ޱTg:֪֪WJ[F[ry"!KZ^]ñdv,U`0 -Dzol ?`0-Hݣ V`O =2ڇxӌ]F$M垉`0  @tSmGe +7b-;e\9ٍ`0  k';}S-\ث?`08?xq~?cpfq_?oWi,  `0^u>ΜWÍhcY=?6 `0\_;z'ЫGy#]';rv3̱HeYF6/u٣jߑᆯ`0 ՃW7 `0= ӎ`0 `0 `0 `0 `0 Sm`0 `%=km <__5Sb0σ`0|`0`0H`?`- #ߒ=憚n'b0޿:`ad"JkRv R#=Os xI(N*a!!0.>'IV`h<vk460cӅƾ?]i{"X6Frqi.OTek_>xN9gl"9>*5+)ُpm㟇Z~X<ށq3q 6//IfztZrM?l_snVZejR8 }h0#QSSC?olC}}4ta +E2rLI^&xIUVFL{i E'J{8>N{*)zɝV % Ň&(&-oj5e3*<g [!^A gd”Jpс@8Xr ʀĂg1Q|i,,"o6lh$ʰ?,ا0s{͎Έ)+IA]Qm_[s 9'i(Y\& +И_03B;1k] +vv9Α,96EY lI.. + ҈4KO&YUggI$Vŧ<~]Amin-h.lKTְz*kPJL +VV:;u^qVy +LLN"\3s󩩙Yy59ufv&fVq_߬?d +RMk+cȓ(P~eL~ T)%a;k >B3Q`,Q(#?13 b늀zj4@`[s :HxPMIFOC"t. 4-a#{_2?.)̫1Hʻ 8˅&]/{hfC aOf- +TB|¡U;v աO"U2ݼ Ny^41إx;o~ְ +fz_szo/K>wprmSqѼ#qnj%S^^EFNm\byD\G#Q6?LhQ'`¶_y9&R%*^sѰÝg{6m_1gN#InܫsD'E-~pRs?IoּzN5lpfτUcEq=V?Oh7{qD + +**r+9<n0s'NʘOKNC?]BË]#u +kϧӛۚi=/;{zi}/{ zj*QJ6oA__v╸ם JXd. ~ u ƤtHͭV[ʜX!uZ}(hn_jtSO\3dS@-?z1:\՞}{P]ҧt.6ﻻ0.dDYV(GGSH𘡉W} wyKˏ2GYyUv;P?NtқF1΢e{oRAȳ3b +d 1 %E=5=UΙ(` `VF ,Q g"u f9 [rE6+%n.׼o9{j u5!$ Pi{ە@# "xe!G( +?l +]1N_u^V5,/Ǯts馵i-ckniW״e&TH|aۮJka(^g=/*)yv#$P.IZ; B8n8HH77XYתk +N +Q[Կ fp( R_(DHA Y֝F1@ h@91h7U, +xüOWQ\0V3)`t9ؙ!@$Ĉ#o'v +^M +LwLC[ZZ^SF QuF.Y+ m7~_ԵBҊ:vGip3_QY;2!8yXN٥[M +ө.fT_A/,ksvdɣ#WcN$(Wb|nٲ)ǭ4eʱwh.Rr+ &L* 4ָeh.X>IVIhdmmL.ArQaygϫ_2u=(lԹG +bfkme̱CA! 0|jC^8>1l[?A#ddu1H@+C.0?23*B->h> +@3F]?`' =5̟&gֆU3MM]55'ITN7 +K_ ~~7`"mB7'lߟnQ7yOӉ+83_KZ>3͈B4Tl]/(hUs?{dž~Es~I(I>GQ<z .s!ՂRhVqɲIys?|^T-)c? I&G#s d +[D[;~W[ mӮIמ0 iٹ.-#a x7 u N?6lnv6m i߼%a)nqa]䨻uQo pAٽǕn'f"_<ͯjɩs r@lݲb>7WQ8I,7 G|Fgpfiq6j /~[s {j v1}4ԁ2΄cvRSU@a5@A -f}f,- s83sX24]PY +sO8~Zaw(9Ōe}+ˈ] anYU5$eU7 +s`j ?eK(ߌ({VuO)B#⨜ؼ 7['n"p8Og&of +,v&-O\ + n&"a r-Y& +lwIFITzCHSz1dlS, #j ӵlٺpjrf>n`-S.SpE3:Qp骅>9<ge*X\$2R-(>< hbDs5Uh:[@5F^ +PRX y xEIkCzO^Xʂv"g"~N+ +n.mPK|h=v uXvAW(,e +O?\̷r띇b͂*܋~*eܻ TϸRO/8qT\!R,HDCٹyj1 a@ +'/[m|sZfkggǮmb^ZZ:~QDoQ۽8ѣ(9*q*g=fFf@Pͽ&[D [DB/6zٹy7" Qfם/V 66d}/j"eN}mi}u't7Kn%+ǿA@`(z:GeO0?  Ƙ>J | yY7 +}$3h*Υ;jIK4P~)̤(#˱Ԫܧ;l?+Y7g9먜Sj|톘ꃆ;N}ú^N;wVEXPKDT#:QLc2ds7s'>j > +C'ivYz(On4'l5eu2Sܞ/6;ֵqq7f0!Giϥko+xSZU^L(_rcǑ(4gwU]OkG7ii9e45gUmR+2jDI{YWWҘ^PZДY1(,^7,)d]^^W|S}8WNGk9Zd!b{B_w=U dPV[dqdǩYٞQ97"s\쁶@?=DqO'̻ȅo="1p!?l(G17xuUKcmn[ٍ>zFOwOcKVlۢz| +GM +}|3O!p9D(?ge%<=/%Jd^.s^ddj>1"~\FѕS3:}Vq>Is]lY? +ɋwN" +Ibo<`hРCU.LI3m|/r@y`ѕŀY`齂Kws.ejy%|?{qN9zO>;ĽĽȶ\Ul۲ @IMTBBHt{Rv3l rdky{[LB +dEJ'?-2B)|=:8hj\nu'ghrG6JwVWWs]ʓbttI~)~)MJ$4C LR‹y#//$TqD%wbWbBxRhoݕJm{&w*F~8v+wӑwrOzm5:rƷV!/ޝSXB?f-|/?Fwm^sO^xzh&V肜2ϔa/7 WUd>Ӵ E ?;П͗ٛho'yreZ߾Lq*Gq ap3_w ؉S|n^[_X7ykz^r`ג/}}֧ pt[Ӕ?tNXvf̟AW?{aFכn +;-=G G'v63˛|^~#lKpM!+sdž;>Rwl7ͼ2=eR!-'|WuшUOwbZƷ5M*׾ϟojn3R'(nNV*Xʀv=oOnq՟͊^togc7Q'+z O &  WHe5RYT^\sGh<ue饩ie!V +RիWӗ_ q&?u#]ш;6Vdž,1EU='VZ<.a c! ciU//ƺwV)阼ئ7,]2OL+ Wm:T\j_}mF7]zhٻ˹=xd>sI&(?x y@xroM(s~K>|̰[Dz.zYon*Zzȣг{גM V,.&Ve\4%IVl +LuY6!yvaŎ" 9wkMnRZ\TX@o%|l{|OJZ')uIU>Ie +35"*61 Ov[]򿚘YW[(j{Ҷ^nOݹ +F㴊,}V +ɧ˭zXa;=[/ mbbO UG[=_z|oT]eNVth׸E:q]ε +(#9s u$W'%fنۇ_Apn$&+F?Ie>Ez⨘WXW#3""?pjxrJVutJK!)) ʪ|zɥ>R$]wo4/Q +3t#yÃ/='d^hgW>'V|VaJ>mwl~~ɚݿ/Iϻc}/}/pKsI~d_W[G&&rdY]BBmɈyȟ}wթuBĩ"f30;eN:r2nﯲ\,6)z!4^^tXl 7S*3xC&/x2FxW2ȳ' 68OϘ +G5wWVVsSxMVo>yӑ + Vյ`㹜v-<ܑՕtNrǯ=kw.h~&;}80O7Ap9O95os}?99rr׿-池zgըS٨Miq___: MP̶wg>{knKߋժךuɽ)]Kuoy]G)^r#UýzKO, Mmӯ}^rtCT6M럭=.vՙVVvvg+2謌kijE lP1.G+恉gR-@AiڇoFLbJfZ^I#8l/;lZ]\qYRJ^YekU]OEuwjv)k :?^n[cKWy/ y#1NoIcqAoKgW7!X_kRYش>ﭳ8{~c%# vHvhA 맲U#qbhRiT +2z!se{e 렡m7ش3hmy]mZpV~Uǻ_?5'o⽏ ^Zy'hK~G=ңkSC['iàWO*Mإ+clw}檳˔?c!WpN1ޙ2l, +6t̄ 4Y|ڼܷΧ=z[/<]?޵:s{ʶ:vA\]zxlF6bYZ\w/<:.%_<#'6J߸qs-ʝutt6]K + g Y9G,[ tOx%~.^쇀 +?!3\ϲα %lCX]ɰ +h+S擥> + f|~þ~~_{dCڻqө~y-W}Mzm-͋o<}͇jsO Ubv2p%"_9±b<ߏm'XZ i2tpA=~ۆ||! \\~{U̟,C 1rtD"6~yϡXcnp!^v?>79ԽE- -GbמǦ:=ef=3: +8s9EH!l|ߋN8zmG?;vZTRUu]OSK?+Q׬wtO)KjKsandW]wr; cO/j<_mm_'VyI2˶67i[rב)Ӗ]/:;24  g7e@HaW?=rUo]\ɵ'l`Km 6X9}ljAz{QehS7y@>;-B)T-u%ahu=f GABd7pY駟ȋVyظ>}tkb{! +pC^F_ E'޿E'ٹml뢴Ĵ]/QWPvA>,;QnRmjA@K[,2b -l'oqQzY^Q`Kps$aKkrxq"c`/:-Cxy:\`{ yCP}M?k!ueg #Rj^|%'_]zRP>>Hyu}OIEgFnSHTKO|zbFVA)?(2 _>:y c M/z~ 5_e2\N+-~Vn2\3F:%l9[.ʵ7k_?gpJ;,]}w$?O+O"X=}~lYk6IcNB2Ǥ1G@L) OqC)%A8Eאӎ 'kANlcpmL*<Rdj\'p4/V?]"8 ypᲤ@ +'D$K,}h@Br p8 ^SC\UE0|</ 0~B>ӝ j&2եxmSH)^i pśbN}'\pH˅N3X%BBrY! PH ' k\pQ!;,F ++%* +)|08 )x, {CsKG>< sW8 S-|'N/ߥJw{8V]W!DݢͯV`.`IC@]!{`Q0Ɓ !1_ V@3Xm)3FFUB +1oEV*GhJS) NְbAfDi +85J jxV'.fbMs1~cp>jhVLt)w>|~ h-Ƣ(&cg & +|PDma +(C +gt(Mm@;. +1Y~qP-nyv(ϣj*FSMZD N.Y +gZmYxCabF+ cQf|̚a ƒ0CKA??i/9?K=ib Qdxaf)4VKÎ`` 9@Z5A)J;OTNR +Ihi` -(;Q~^Ttc#\dΤs!1o*te[4쁡sO) <C2G{I|@gQZ_ +/ _ZɿZ ?ȑgԴ0(e ͌~0K<(^|RYa`df?}v'2X(CP^fB)GZc?s[PpRm!R':su—Ccx"'L + pR pY +`2Ġ.`eTg)7|[/ p/|/PM=*??77:Y猅2|}MSc(HpWR#jExb`$"[낍'f +9Iʛ@.T2Dw +ViUh3h +e3Xɓ +v?jR' 60[3 +dR|@vd`,f `q&|x e?9+XY S@ggT_S3?8T3T\Ǿ#ёR4ʶsQ#d(8vL.nzp-&߃w4&fb9 LXdzoqr6cp .l9V1MUC +vJ~8@]\{4ڛMgC9-?7|nAOmwkTє:F8 +?ي5>ҝEL YSܦ!,9IFN&0EtbF>rF`:P ^^NcqXrXf8V К|) +~/MOJ u'j_!U q P8tbi^XȀ` ʿM2,:p]R. +?s#tQ'a=F! +X&TBࢿ@GyeiU +@-Ժ`!JӾ6s˩X,(.4.@Ky?15߄(snFXϙ +ӁTMJ(awt.V7+;01ɁK1I< +nBD'W> 69i#\4E1( +(@ wbU!VYKkU PՔ2Ymţh/|bΉ +:i&Ձh`7M.KѨIIs<1ϫy='Y5aKS-&@ _'}L>mI+{nԨgͨG[%gy%j&(9O̜?zp@j0UVDts]XZD7WȱLzmTc}-U)59pi?<&Wt %@M\TĠ?xrP) +?;ɴ%x_0ZK +ঘA?tp.ՂMZ5f0Z/*Rz89/Yj +c7 +#+ԈY5DcSx/gT*ꂎCpR^HAs\LIjx!?dEwЗQ9M91^<_MA& ́e0cW7> BXF m3`(^?y6%aBoWPDS bfG=NA2yO-࢚137*M;ЇϗRQ;p%wx@=q:0?,Oå%hAmr4`- z63"P6FUl"VjJ tn={0S\7kr6SM,Q9@e8J82`I g +=NkPpd IǠj*jke.)~Yz O*i$%:ew 7$({SۉܲFjF>M26EUG%˩#:0 bi -]ZXh*$P +%F@h +Z~.J|thcy4j(@ ;UEjl>j|QkpZ+Z1W$$_i&h6Aj̏Ev +vr9?X=USXŝUIiQ߇'}jЎ㬀!mA)k?=Stw^,?nRU&?7 +0YcO6f7ЮDڔ"i9`UYL! +} :QIͬS7*~-ְ`FUe@#s"%0#aczRl$>i>o5hQ1*^ls5Wn_v3Ҍ +FXӆVm>ՋI~5ɿPj5̟SQP7`pfB"A+E19“|IQ z1Ћp+%ÜpQ/e&îXl@g/-f)0$9բP!qSYyRYbjq< +5XuX*&kfQ_ͥo- ׺~z~n9|.mǕeI&J%4 PZT-n#1 k@k9_ʡ}gKED 9V)| +h@?ZtzK~2 (Ť騹H#1@8k.AN5נD2U3fXIK9EP5f +u8=3t_Mu4u;ZI~*W#>/L3kVȨE8?E5jE~q$n2M5?9́h.W~9qW:FB3jĀ#֊w2,'2HݿKY=qߋI4j`^N)xәh~(6Kkę"q2Khq"uskJ,UAIf+3?dyc,TgGVI2EIXu RN RM}M`zJL1a rgO(O.5P VY(_/Ι]$r +~QO"7qU.Rp\ħ|珍Fґƪ~AŝvB;/?{—[Q_?u V`> 񏁪G+ UK}·j(c-Ԫ9?)XFzM03o@Oz9(ak? @LX.TƎ`F$p-TSr:YFhY" +gcSTX.^[fh +nqvY*7W03G3Ju26񔡙k%RI1-Jf߹hwy,S.jc ۟Ӧə`=%IĚY.XpīB83_} 7雌;DS6s!ŹjŪ_}U)!@@wb00w 3ˢ |?::VZZ ܐ%,ët;y+튟:_W/G3N~ 5?3h}J5O6 t̆`z nN^,Y80l?lf>ͷގ;a&jfw'cgv#]uJ*J%SU*# )%ъ@ " A K +?/&J[Jߟe$gI4-|,/,ǁurTixzhs2U=BH*e `M) +Es/C6 w.$| ZP}tJkd]@G +Sbˌ>0c"3 _+;0KEd?~!*YNKMUj*M.뇔~)7 ਫeq =S"#d\-N@\; t| +?8 =Ѥ%8zZ +!%@ !kI+P +٭+8y' JE`-t.?sY.2^} 7D=}}g/3w +q?9'*7 LO'yZ:0ڞ< + m2Wjx ++y!ԍa)@_O} +y;T̯N +V?) gYNOt'lɂ~j +wnmb?tpGym +["@V"%ճDt*M7xwuNO˟ + X`U/?u?z {ц?7LHDy__B.E7A6 { +C{0 F^XAg ʕ-D] + +Pݧ=lFc> +P߹K}rnA';m$IXBnW +1z_X;B.uЂqm/#np 2|kZ4ߒ`7Wzy/ob?@g[hig0 M]@g d 'ruk|e - ˝\Tа-O5 +ڊ.B~/n7׭I/#fF +7ßO3/aO&?)aeex/O9== o' Ep+t>Vxb0 ++-`fe +vzF@ +CF +9W> +e9ikmk{䈲#\?$}M+;)KI*?Il~&p>IX^Fc&,S +xqyi=u\@Wy?' ͯwE=7$wf+< +z\/C#UN)SpS+H 2-T)L V鬶[:^?u 9E-tU@wݘ9dd2m ۙN jk)aGmQJ EVP_*8:FNZ!! ] +/\*:0ld~DexRBY@<_&[cf8siWpB-@љ +񫽂A.-y`+x8J)$aZ&( __@auAlt%gr~`t_z 'D|aQ8zZ7Hږm]`'0rƈwdcC~?K{)6ʛos; \秎? F= +?~m?wKxGqVX MRFi&A"]#oU4H\ i_TN8$qg`ʫ8$=v3ڏM.&_!,n_2?I<6cH>@!TOB +`5` +Ϝ4Oӷ_iL2 @ +c[! U@)şҶS#d+]=ര ,t#TLQplV='T4 ? bY 01CmTb  +GPaJ12{ r?МR525X9d䫚#q@T P4/,:0 6IK +&FQ|`J2J{YS0]3wr=P6q̟J9 kD^ٹK}K ]+?#SY+(;&0gyI +=D{R9J=ivf I "-Ѡ~ ^[t$vHjQhgYxÈXI +ubi`n(kv[_JD+|h-yT. ο m,g +i߸T pXZSrP6H"pR)"J0'/@t-e6ɎPaY%5MMkf)? +k = +4g?E:jng }UzޏBm%@ W>' +r7!B)XuRbduA:p@U,8uMqfP p|$F@.}}o'ЫOQ<1'V/X3NH{'ߌN.@P +P-qMc8eFV )MЉk D1xƊ +xQ^9M6/|)B p±)BW@e?3ew +E +zMh{Zh(VPoWG{Kd|FY I,'iu9RO;M) %sbhc +jDZG w☯C2 +T7K`. +F7cRI'U%1h[' 5+2ћMi TC~-@[#Sfycl5A 4U)tm]Jpk5DT)b m0D~'/[g>Yyj>I00"%Y $$3t +%WR V`b_/^ b$0= +SՂщ"~YZaZ*@Kvʳ&V[TdMSB3 8D?,E-E~ pEywqp^ + %@6Ä*@24OB? -cbO|wx#q +o +5¿fm85f3BiܥO +."#8j +p? }2 &.y +z +L%׺.C&,~x^=U&Sr,VYړ'+(/0zPmoS0 +de1~&ioZja!\ +."- +V*ZSۤD+lϒ$y2 ??b31\9 +MH[`|)`e@6`? gFr[Dqwz{Fɤl_NQ |Ņ +e6T <<GI +9@)?1;1}7Ea%7; =*v(74癍;*z \EG { e ! +}>oX\F +|snp! ~Q|:>~JEbK_pkQT݊& +5EaqnYHuDQ + +zоR-Il #" +m_PjVk -s1yy/㶼Y9m5 {W xzt1EO"j6wCWҌ`HҴvz'I/00 + 5(uy< hɭ|_4L>͛ +lR=5JT+" d +91~;%w(Z^6Uޫw^boϟGu>Z^{T=|-jxFY'C~tgə8 +|uWr-Ow5ຯw!D_}>!g?dG9ר" d&9Z#,mYt!XDۿ;)ZTK_R/5H;%/Ä C%(x3p4g +"RR ") +^qw:t +瓯B!t58w Y*A?$j"ƚU# +da~gP8Sx [Z&>83n4U[8۰ 5Fdcb2 + <͋@p# SrJ郥tM !&# /b5J~ +.R i@97?2Î + Up;) |"u\!# ҹO;,z\=9[aos R zk|Sdu#DEcFYUeؘ7QF]!{˘I>p )ȥAP*Qd014@0N+ɤʺ)'# +u +۱geI{WQ +#3Wex-{PuC/H +$ӷG)>fNΊ-8ؚZ C'iق)Pm/ѲVA'5,jg?%+q/ +虊u+T +ld}8kҙ#!, +PO(JQ5E' + +  j0 G +&>}Y +0˚Ǖ +`\@+:^Ŀ"Im +?|?hsMb<ԆV9 +R"Ioh_.9>wM +Һ —]0ΡѾJŲjQzhU2s"+]I"FNupN/~ +@.% } o"weDX\|G RF.Gh L[Zg[ww]lkvI!E(ש.zqv zzXM{_P6d0㦏Mۧ^"ɰF.b2z>!X +^-\G:\(%ʫ=",H&ȧ +P'჈^T-j5Y +ň-25RjG99\s_'u 8c=$k_p}y? +% D& h3U_.UЎ|FZ[SK'scUp?ޣmbBK) +@Ob*YhRp,efF|C" 9G[ Nn%h<+ [its@)`.M `ݘJAz/m +X z]j5?Dگ~wDр97ҋ_oMSJ-}{ +EƟL9I|?7zk`rF +ۡ%@ʥJ/ٷih$n{89 ]cnzW]Ui=1jiW+ki24Oҷ +h)W1 {3P>LJ`Ȓ%fἡ<@+#aZSԂVb̪BbEu&h`_޹)fGH!EpVORc +5_L0J;'\댥Dи=Ii@noP9 M5q@g.&:$%@ +!0])h (| LP|Td@ U(PQ#BaF&ᄈ2'ւ +cØV[.pSQYpyڏ"Ѫ $(9jdyZr;EGX-^#QO1Eh``^ GPRg?}Kߞ +9dD_#{xAك*?I0c˳̯\`G`e!2T +_N:zݧG~) +x.TfK〃ߓ^,锏Z +*uXUr$K>@ՐA rֹ-MT4{Ň.{!?TÙ+"R^7k 9҄!\A >Cyp <\R cu%MNt P5 YO@@ +'`s# ^)5`S4x;zŬ]fq,96,$]#w sSa + +q 0]\K +[1߉V!/ZA ZPP0mmЎOnsN> + +f+ cj9k\"ODg*A>R^9F=-$PFG4#`пcvDL{9sS1hF + 6E-.n?5=CNNpUWܸ +bO` A|& ]dJF +8$P'G!nk@qi/æ +O#leݡR*I2RqJ1Q + +Я6/A5HBf%jb ށ[KRB_WK~lvFFg@f jw +MLojz ,=t{>tG +GvR۪6W |a8vVyNFR*3(q]Cd31B M} +e?c M4o#: +CGpQn>]'CąЀ oEJ]ޥ3O;1z6G +Gȱ.CK+.Q`Yhא$$\LVSh|Uvi݀20CE2&ߍ &|-k]P%O Pna|J?7U{JɁӘ[%_QO3 +SB#]rLAG +_u2?AG4CAJ%YO"@[B^v߀q( +0 +e +6[h>Y!Tssn հ qZP v4j ,&Dҫ`~5G*ݗPSe:.ڍ!Rj'`A8)߅fءuKVabih_k<hWobz(?>*(:h@Y<ߓ/Y  +|!HbVeyUvGRа,R? &? +0*"4`ЋDzAoowXVKr8ȟdpj-? +xi +o2zwov?'a?kݫ*`yE~M +1veJ`2!aYj^'ma,- +xAu(L +QYFv >9tn-5yBuh46N9y$cʆVGcONCQ]LCZSODZ7pҹJC8N[1/K:E~4N{rW*yzݨ'v^S؊78? +m?Jܡ_ +'"a~[Y;g#T?c @7Vh9~(`5X]kT݄ B* śR `Rf<#E*T3K) +` +M6+md([t9Ny>B*F<wQOND +z"5c_U2#W + ? +/En7}0~#L`s(|Qh XvyA)/h:ri*@S:6&-1 S +r-XOq`d[>^]R"yv erQa[aw0L& +|w䘩B7l 0m#X@2"ooA9OԠω\| )OCn^FOeC('r=J@A-kϰT8B% WK +YOԠD:B=HCd%љ o"Sp#XKs<­H䛗]oPeHzԦnøˠCX>@CKn? >u&"V +(Hj8%/VR| P4{ 9_j߅. +f +4/f +:/"AEk_@zO/]JC_@fҝp( + B-CK \DaOT 08(f0Ⲁ`: +!0*bJV4qEA=}U݃&b?LQl Kxhe~io'l hHG?"G.ɺ,*@'wLA +-Xtr;#_f¢:ŵ 5v'Ü zYp] S/MŶ#|2cZ 6-aYkD#j<(P +sF +/_w#]+ +۞o +C(b 76 +ŶvNtz_m`c,*(x +R\YmY\ +z*A2!F"W|:3b-|Cr.[w& )WHc\U%{n2KGr]qρ3W" W9 +ۤ%[~nC +@o +^03 +u|Ĩ + ϣb߸ [Bnq$Tʆ"pb17ncիZ[ t;(G3gau7,X@5S/K08q s5|GWwx; +=fk_S +pR- 1R|.0i)GF5)" +ԌMPG)|A4b.Gid.=1U[ETEQy磊=[|D#ȶSWHzO_?])հ\Q +$ +G]Q#WFQ?Să +iZ +>Nt#|*$38坑?n1(WW)]Kr8]'}ߺȾ_pZ +R i^ +TMj+ }t7ڏ]P c@&4 >x J[k8T>GE+n/L0s L_uet*'J +P*h)^<5-;^)ɾRYVv(@"/[' sZD +ⴒBFRЊ{Zn +<EP)Ih؟8pVHL`! '\!F,:%sqvSE>bol# +J} +>T*l_V~q + +߿D^w \Lc%_.(=_ +Ay٨5dLřp p(#)~FB1AAKb"-myQSh1? +X?"}pd#RY@D#xV Ok~vk-ZOjρ&fC Ҝy&tg8hW+Nm' Z&El!C2#Vua"W+X K0I k_D;K?'dYqā};`/ײ}_>qN%M +ئv(f7k)ZQEbf\1(q_cZ fpE{=lE_a₀[ڑiYv8`[rB%?&&qRA@([T˽YqNO?A'rcEtӾ){pi8ɬb"V*i)):͞AZ˯BF@Ew!Bc)>Y[HV +jvꁯKV Tծz " wa,Qi![ +'=vli״Fnlkx5QC5}!s .!´\`.gUh _E{MQ9bu{#Sv w o>&(9T~3rٱ%Ѷf=sͥ<EMv'}p{ +M]40Άj6?p!X1~=ß`? =Ŷ,PD|$y._=xUI+<0oȬ'Xzaū2bJPp2r]k7zζ-D0jZ meCj [@cl4PlA~ %ϟX # Bd %K``_b 5DV,y2XA2yudzn| r Аs>')2QZ@% +?QJ^[uKW\oO[n2~ o?Kt)]WoĹ]i̾ bB7rM#w"jqK"al֜D F@hȯعc'VGagM9#t7ϛ;# +I:ޯ:/} +7Qe wr)@jep rr-YccB?GvV,+n+0|QyǦ=?[xUĠ/9 +ZȠϧJ'p:{eCniS$0aYJ)n>¼LK9H +d} Ȫ {2\ (ϣ 8y~CTc|?C$]A/45F +}e/]JC;BYȱ=P 3/tG"]Z$J +i_.e8Cr_Bc=l/LxSÌ%KC4Q_ +:PNB `دVP-A7:yX$ʁ"nZ<PcXA?vAlǺh;`y"s37m +;qC4̼WH>\# +881l>:A ?3OZip*\PmE/S* 4 +QLO{tűكF9_(v ^WL󿘉@S-q\iph hb߁* +?g 1ro_HiHaC8g!M es^(SYSKr(nM/t_%pZ79ù&1{YM4]a# +. ,k a[f$Tށa>-`킺IJ(MQ9&dduk6 fNaAPU =̲5 򦻄Yz 81^n|[7 ₜ@pNJoLx%h`^'+"@=~-ԠC`B23Y .!L^- Q~;@X@`aKNlC#k7_= +W*|HF +9Ge}&0P4 d G +_8e;?:5t +~amQh +#YWoo(p*BɈ,`'@ gy]I6R:X!q4]9s Ki!o?Zr5@h܁B +Oo +t2V +/[V;Շx'oykr鱺*@;c[Va G4kܐz j)m)LD}- 2H?*_M&SUL2Ԋ#GI&T1RRqJ,,8yR+W#pJ@9ԢI~&yRn D", =J1P +g\лOm]m HR'[D'5973 + +{@Ґ:w"hlC[Ҷ}<,Ǒ9(sѲ}HtYRA<` +=YYeYĸm|A4D !,6!]X(}5zNB(iEBݳճӁo(Y_S^l+r'_@8(\?@W԰h:424H `_G/RvAaׁWh"XD"5뀫 T +5lw? ^=BeȕjBp;cp⯪ltlA} n>3W5 햖H9 Vu31Y#HHbs$o)dPoRQvSZ#x!K6 1Zø=g K7IYݪː +p`30 kRa @k`!,)0n'G +%hPR^*jRN_V2F6o*#ǫ+Z#XBP4ciplB"o;>|1E+v<6gjnܓh+Hݵ@GC +'ahNnI3[J!ÇX@/ x3U_\FR)IFEķGY)|! +`b؛ C8ϵ2ɟixuY`#^ qsfV(Sr5301?C!@~gL랸1"VT@(i>$_Mr?I(-´47RoyEWx *ozY 2rXiFwG進(io܄g9W~|6%/C&O ;{mQHTEyF`oqD=iv'u&myZˤ4%j2WIK#i☎p@ւ tq +25@>9 +}Ai_" H +/3rI2QU@(8a +H5? bB)yڢS{~"g-/9 +8>s!h +ȫ\_6ۄumd< +5&(؈u -b] +*B=sRib,`[& rw"5o◳aʹN7TD7Ⱥ* +k>D7J ^/S,4P=- +~XoBJ +,Cd4h}rF +ibpGG}~FNKS7q8O$/aG':ِS +럨zCMAmO ́ϩn2!_mX0#V RCB5 Fk-lR|hBE*ϩkS(#+1یW 07*B!#@{#?FRB gdϿ|w[~.`S@| +2aJ\e"~Pv{&WR Q4O-W-E;0|8OHA0wR?Wep&snb{Nazxed$2C_SK!лQ&o1o^{D;yE-: ol締Np~E?zpIV9s0u 2T +Z!׼C #\Z< +Ga$1Jʁשּ;2h#5 ~P̡jh ^zi(p&M==w=(d7(nf0LphYʰ:i":N 0s{Ls-[{"em10+h%);+ʮ=n3"Euy|  ʿjR|BIo_3ߠ_u9{[ + +O*J:.#Lb3>,]?z[ +tFZ ++𴬀2QJ +(P~ ^ v@}~j/n+6/)O@t?gsUR`9bLaWg< +-=_ +H@c +.@/ +f/U~U.jש(,[o.7-ҌܡL^ՎntGW0fKv͔O9s\Y'A;LJiޒ*7*%Qp +pWU(w'*۔ +a pKP_hd_;\(XG;SA597k% +vA S6.f)S~X7o t )EO|Ϫd--,{#S$yyBήΑ9GǸVڿeC΂)X +υ%_ +xS +5\z:.'ҟ# +~cdN/h`MLvȱv}P +4ƚ y69RHprUęF"[o2.ݨ0:tNp@/yDiۧ;=m}U02QcIU)aasBj*pٱ7ës$oq27@7"~";tUdZ8L~H +ee<0|J +<7lxAQ cr%@Tn +&~O#I j" `",Wj9y֜g;uv֜FIS +Ý.96&AG:C赐SJ*<_VUTT3>6Xw뵧Y PqCT:^ƨg#,Ro.7emfJT;H1>O[t +g9\GF錹CPttEnc ,mbo(׏y_h M ;}WG<"n ;_h +PV&wly@ <+&28? ,t +ah-M\ut 15`^ |n,kaBsӒf'ᰧU!.,<6p>RxF~0"Q>q~T H +>GN(,?;% +ApFwE> +>ulL:yD6m +!*MPk\ëmp^.v}Žn3M$6}rI {Z]Oý >gBr5mJVhXdQ!ۯ] \D1ý:ʻOh^(j$cy"Em˵Gr%@.8ǮфNoV{ $2 +x.@khګT pD~#Lq+ȥZ +jP"?6'BQ +D H$a +4N7ū ӹq&v( {+!90_nQW2T0jf :9k +(nd)&B Rv~5?#jиME +&qс~YV2G͙.7Q~o%#Ol%{WWRBiׁ2s^K'D7r]BL6-*H7nA>IslVtn[zB՟#%11$uN4s[%)Z`#Q$v ."o ? +^uER؟w`jg}%鏝W I lt۷]_RAڡo38 A;qϒ|&]#UEȄY}\)uo/]ųL8Z!w#˛fr2Jd*\ K==]=]6, )-" ^ +2`"k);7 +HbH k-R>"D>uv-+qW)@P\: +裆rp#˸$ll9~+`,;x+.*'D]K|74̯?NC$uњ$|N?5BL̍C;FR0s.|#!9lt\ [eFi2m13_q +}rJ$G&_qOLvݰ=&G_-? +HNr> YB9~ Xɛ… ^C&hE7q1>1 +)O}.ߧx~gI:[aE_y[ 6 ocS@ YDcd7NJIzy>MgK +_ߓ߷E)`I!Mr? +ˆ^6cO);G 24 L~!z$ +NT P +`G2 +; ߻l +G_@>JC喐~pxLNuꀲiJ/Oj?"[C?GHSX>IQF +}hI +]np1! \ަpsB1H_{-NS+n^k$w ě <9ۉA6AݬI_l.CR<hS5hw6y֙(4ec]>Z&R-e' +',-SDQXJ +鳙-v}r;Z}I~=O\M^\`2%> +wr'nG}- ٻ>U|{uu}H6*[} <$2^:hK +G=T i.=0y%_X@d)|2 ¹ %I?)@{=/Hr(r- E=8@T4<ڠ'*B@$ +`BTDZ +o&q7.[D;h1I JTAv_ ^O] +_TEH + N@J +pӪS 4U?'_xrݙWYӮ? ȶȞTw0B :#0]GTat;{B G2%`Z%|–mBa +柏e:(<;];XK +X +0LG; +4o!zzS ]w/ &r/0r ōRh~t㐡-!$ +}(rH"_](dQ7@9F +VfLŸg Q}CCOJEa +wGߍ( 7TY4"bu_̃{)o*62$|ҵE0;rKJ+6ov;LvS?}E\h p4/7*z&#`E`lO96ko TS=0 *2f`?jT0w2%A&G* +>Hc8h +šCE@w^6-n?gr.L G(4"Ps6VuU)_kf3B`Wp,Rh ;Q@? +&ɏepbok-i)~!G +X˜Qr_\;ls_dsݕ]ؠF-^+&ؔ!j/-LR"o1G^p,@SlMXa|^?DXT" ijPe)h. ,t5^ + +7i +pA@3Ql 2]T:^Mԍ_*ʊRS +|ï`g%\a!,XCb?k +%*ɱ HoWG=U0=&Sڬ +^" +P'* +xwoMϊ + La"[Q _;(oZ + O%,Aa|D2u*>E7ە +`R}ȍm imC/9^MNH^^mB}x,eF Ts|#]틤i~<JTO58cޔ-k_{8U +PodRW]X@M)q;'d0g-. Qe>KpT[kְσ}|ْwaCN% ނ^fE~pDVȇ)e(_c@D% +T@K}ZB>(9c[.H.@y>? pv E2 F +2Tg(/k, +Q?φ +L_Z~5QDeBy(6a +q~"py-AMGP|?"yZ9j(5Oىa) +ko=QOA6ltW#Bu:(!D;s2s)m=ξry~xEʊܕt3:12)*~GXy}T1nP9;oQ(GRR2G< h1&R%/+"qB* l5ls% +K 0/Q}CJ7CpPk/|-UezӊhB@N`kS硁1xCGZ@g%R8sf6 '6iuU@ZBL@&#hThL +bq]VԭΜ׮`D}DOB^1;!-".̓s4(2jo_!|aa9PN I#? ]1IهQra~[{{:"-4b!9yox=bؓ_H[1*a-'Qqn0;J +"w5JNJrH '=BB&h o&e +W^мf+E,^~xR.=_bF#67 #H +u^j=eR]F 5) Sh@޸rfPfJۚk +J p&5s>0KI^b.|"Ƚ2n_ʼn? +LE?iK@+vcNDfI޵f0:y#E>WD F"HQ߼/3v@F/^&?gܖi/x{4^i, tdő|.fW`LD䰘)4rL$7=jnSXfG@j +cf85} 64F =5GQW_\{tmÃ<%׽u4tsГ}C^ësj y: +Bk:pj +B +`- +rEDs *Ϩ^gQ?u۟I?V9%$^M`MP@K\ dW(!1}OR +.),Pi0M"4tm|+S;-/(@K.L HV +o#)0$Wkd ˦+EQ\"+F5a*Xhve, +yq8 g&oy> 컙aJ$WoZezd!$? +woQ y"GM:pI<=d^)T0u'avXw +a[QxEpd +Z\Rp + b,*j$O6? 3 }}&ugV` +: }+#xzBׂ +*Q/ \0!_:n!뷂?q;,Ό>N +BiK0:{$ҵ.| e},sx!w^䝆3 +kC33!/AGdkHs. 8I}0H?;Lc ^.>,et ]Hu?R,Zh%!/|i ??05F܊J52!a:T$9dxoP?B + + +X` ^@<'$i />#hbXˉ +JG C|$'qA6C^}5GM`A6"%gd)EDI‡M:mK +͔:xǩi\@ +sUg{=i7M'} +-Cϟ7˰\TLؔKQ.PqTʝ +(ƒnڑ#o0 Ѵ'9޾m/Mdn܀aq\3EMGi޷ ^hoG=u2JaO'T.ymQA+P$ +K[D윒}_@y(1` +wR_򈤯*%&ED1B+ܤݷT +ތO ^\ +- 0Cxh{=8Q; MJb%xB + Adꟊ_C8@F.Uakݣ* nO s$b?qD|EG  c;0dch谴Ns8}GRcaVlre^dwaꪽzBwK +(LQɾSx3fF9 i*@o󈥘iN-*_A6`Pe7Qs;0CL~}j~W޻q9e<2 hY@K>+kȪd h#sVP$3 +A徭/ʦ4Pf{6nۏtç:"ygD{W?٠ +!金QkmiVMb*@4+lA.R[P]fk--rˣv qwJpwa*K|%d, i>j) +L^#sS@K.05p5U|@0Yɉ#h} }lퟓn" LnIT? u:^>ߐl ϑP<$¾Uëؗ + T!店齵Ypa vP9J3pCo; +jHl ʊBVX}>TE3ٺZ5ѓ61$pFV +=/s +n ΍N*j"# k?|>W,N1X&_Pݩx +|幆o1D {dWS?-/rۿ^zouDy,q +qhx8vhL0a4!k?7\?(p1i~a`7qJ(I2G;,9+#H34dF85+"G + Ձbr +ȏچ9i5 UZ +xkH]S-'.//eG P Od4%*E4\Ugȱg?LAK,*  +lM7˴$ttLvK5*fJ +Y]hp/ o!/|]|d));NאShjާVm Su+)aV0̯\ Ig]D >GL?sdV$e_faCZXBi(* M%m!\Q67O9R< oW|к!C8#ҲrAbNe'UJU@}*,bR` 42াWx܀nQ"Neآ ZU"[Z=3>fTz--\k(){.4xxH?FɌW"& +SZ W,ǔ +д +0&J5hXlA] +iZ[" 3͒مiT`(G`>|)1H`gWC߁ ZM= +l i cPtA$!|ޅM}Z-hjf\o*YF +⪙ +̷\p!$MܸP!NUOЭCl^|:7` +# H/lPu{b[pϨ<ˊlkhF@se 05w28دuLv}˂;t7 @6{ncǶ\wPH8ĺћkԺ$sj0x %T%zƱkJi)t{̟AIAEĢ1/Jb*ƝS heR8<$ˍRU5؟窛YPL/u5Vat--?x՘BrnFEDaHLn\7?U⼪Xi'[lE/i\K!2@""8G$2gQ>3d?⪽Y +.{0m +S.2 +A"@)DR5kJ|SQEHIa`?`r@}T/ (Wi-C{l>C pv͝9&tVy92Q7yFFVu"X3j[9}`YPUޠP`ڳǯ"8矛DɑHD {eR3NGtB`&]%fF"͏"7C@} SA.Ϡ\?@}Ek!c\&3ߣI%4+FFcxX-ѽ +V>Td=.ݦp#Fo[y yCVGP?3G82ߠt8:.R`?_im)#raV.s5 *3w{Yh'W>~#\SuρsUC'^B{ /HY,DER)h }P&6ta2 +V3Q%@H;o̟ 7, +9 $o7} +rMt +@A{9&/r`'ԙ~rfg|N +@#ϼӁFIۄ 78,T ٍ}w:xB},f)w&^ +>\ +z,IYzXeϠǜ}DO@2x5A +;;O q@ω +xn?^ Pw%Z{PߍS=^|0_6ݨ!Ŗ5s^g|poY:H]0MJ?_tIn(>dyh,A  +-G +_6VU?P# Ԓr@.L2¾1Z)=.HdN)\" Gѱ,n{L/O#s'fc^)`lJְhI6M`Rvf{@%W23x;I>? )$[* E9z\"~q;́'YȱXxl{kH(HbD_ۜ "Ѻ  ^픹qڑrՀ_U89rK7sFX@&/Y"q2G`x +|ܳ28Bׂ-UmSF +"Pyچ;)!ĢB~$XHS/ H \ {(C +`yPMpm:HF +2҄9?y + +`.o4@[ IOO7̉`.6wcJb0GDhG%vLWh *V"&6]~A8T(CJ:0԰m{[ :b# o`/ +%;)3 +2;&JdX5?'92Ǒk0LޔsJ 9)¯k`־q`|ϐWI;  +}RJhؗ,WKo t?@/p4, =hR>gxB{d_?KEQ}nY&9 {M'q-Z:]&A1 ^ȯxS@J!Qڵn@uu)i +ܹ%,\n +89o=J_^]Rv(#"} k{),Ǎࢍ—-C8'q+Є"5+,ƌ1Ler +u + T;EqcF +ug璶)yı}U@,Vl -ątsS7ނy[$ɫMr$^kO-zWTĿ9b>+W[lSg.~(rRv.WMH{g[_[ݿh)IֱYK߃I-U?ht|NVT#̔ J~?<52 }"|F.79.-Dz_яr`Sɫ9g +H9Q?cau $>:/xYayI@+TFps<*&T7x%w#Ծf5:惎 +uJ8}%i +M5 S>iϸQvecbXc?؍ hm+;6aKn!;i~3{|]%oR}YuP| γ@g +@;ZMP# +s6ȅ ʋ1ǀj Z$ԃ(hU@kTX?l4/ a{2W   ` Z,O%: +ot +u93G8סjx*k_KjD_c5I9W@?{. +ϸ9 +[Ty<@(pFU7` +ZW*`?")xd+4ZT9`U@@yRlPN2! +oa^ ?W)ͬ?ֲ{r%/$OlW8B{TwG_ +~"qO0#e IJJ +h-.ml5:'9YG%ھцt5"Y5I8|iO Z TcϥK +ٺzqxqAUb;N0z4E[)s-ی Qg܊cet* +z(xsrz +߸n!o ? ahDrՏJ}d~ +{]gu z[Le +dt@i +p Jgxet_.9{[`k_4Y +An;6gR;R7\3Fu#f+$&# tտ/ >ClXOW (elsoްY6dOݯWy؟,SX + MBgCUEj *Z? ms GD? Ӥ\>G˻)}_s*f jzrNqр*&XSR<@6K8׸&t2ZO# +Kۿ~+U +_ oV7קּ3G8?!;y (5 b˞V$BBa}czj&off!뷊ɓh|\2AgzF%Z4`)UFrk +k.ۥD`X +kq J +C`|KԌ/;wY\u>x+deʪasl[31+䑕/`ލ2,şM2T2TK\ax24A +{G +lB92 h>,a*K] ߇VѝƧh1C dӡ{%EϟnJi&%p/ w?qOng3G8"C0w"Nq"Ư@ 1|Sk4IsY5I?Y0?9+˿@J, ˫RD#؛U^;J9ZMRcɝ[br +ԏ +2$3% \ + >L jut +H3OD +ֱ/_h.Nj `=#&WhD@QA*(C?B[%{ۊ\a~^(`.vuBx2ړJ_qzT +`#=i';C&)UcݴP.0Mz?Roű (A:.{aʘ ᜂ +}IA'&\͆O8a,3$+} ^sdWnH(i^K]fD%nP.em}x=E jm+"EWOJ_JoOIlaUW Ae#)FM݂S* s(8;Rv:ȿ%aBiҙڋ`+NP\Tky;S BwP'K#2M$e (ȭ OL.528ਂ˓oe /B`Yw ~56Yge[r@cԽ_7(UM{~0տ#o%='9'3b +:.MvU#5+yR,:<_oJ p +ً_K; +w!x>x-TM8[7]ul ?VPԠ'T)B+g"Ϛ?TL-e3.kj892 /m[Zq:VXmw~ +B` @t6loE쬹zUupYA} +uiۿS1k^)*)?-{ KR=,-1: +hh/Jt{Áʽ0_,Qt½HX'8XX7.n] +>s! +r7~o p~c‘#˲^+^l s4,w d:mW/(uST<>}> Ie15Pħs~ZӐ[%Oqv.*F +j-_ g\T&m{?ʢUX +$p6(X' t?T !TL jFp'3>Pu" ܒDa%$7^FW+l ,i ]x?|Rrׯ ~QzqkMWgHdjk 7  Mr}_~Tz-B-c&NeӍ{~yvqU[ F1AC7&Xa 60ztH>ICO^R +HcnB >be +D70gI{. ⒼE /ɿ_&M#x2M +kWxi!% +'!ġk1ꑥ +V4-Y6A Ok-7]`wt8')0]=ˏ(1LYvfф8X@ҁ?bp/J> Sr%@S'Uۗ ̩|_IV-ݜ /\Ks /bZqh襹~뻂@l#KR5 򿁙IYЁKR\@Mj +hRa!gQM%V§OF9y b k~) +e-jRji"vRlGtO +6; U6հ%|xX@𣌠6J^?q8 Q> +_k)Џt'+>^%@t'G@B `Ȗgr5{G}f I߿?;;~z| Wqh. %n9ѳtjxpW“y]v{H |ʑkK;_bh +g/u026THb)ԌBƠXLF ף+{Z#`%|@ + +VPPfO;ToH5Fj0 RJT8 :`J^FZ* 7?ۊK_N)k`L2c$['w`bXBFAEKl VHhcߋn > +C*#)@;7f aP14#:r A +JЁngMe8ޥPT@Bo^MSGʠ%D!GU +t)K1X!ZHhSy, ъ/̊t-lTD +DT4 +Y9tL5=ã?)`b0TQ# 5jHK7 ß9P@/=iEk)45;Dj_ enx:tAku|?bHEb +-TCa\RfhIzcQ ߍg[ +|AKBFdE8{I߂8JWg!)_)# J69NLa*6qP3f[C -81vZQڔq2(69[ǁ):d +SF?RѴ/, ~ 9 `_&V'gjKpM+y + 1HN] rVtH?{-_Y|ZZw_S4IG}B_*J9EݎCGT:bı( 0_nFem.Q8< Z] +2JsNAC"RmDG=[Dk2G[cv:iXs?܏YT S64mRBG,9m0|2Q9w9`}aTcP@MVW)@:2V2˦ +> A_̙g5Vp9 ++qWXR>1cD(\I?5`lD +_hQq#xHU}&l؇E%eY] L>Iv_MUb v7ϋϋ o~h?;2LsVZХ3F~I7"4q10]_e1D2lYF9\s|# 61j|{RL]y5 +-Dv9&L@d yޮo‹ 2` z/}%V| .k6bg?qrߚ"ط?5h+2@F!MYyrP^>+^2/jc}t~A[# +jR\iB ~?n/ Д*1:*;SV|Eza9krf5bS#2&ҁ{_-8-|>W &!O:' +STd91cbҀߎCG ++݌_\4SFUd%T /[st9 +@[tHf@] OeG,,AnRV–v#J +.g8'zlDC&# e]h( FWy`-e]qfóq>/6gft + +b^ȭ̈́wKCVPx%V z-fQ!$j"oc>| + @P2/0NRN+_7'ESk Bبq~ +(JYb]Y.ND3*F3wejNe)$H,Q k*aoޡj7:$0`'St%6|`}Yqfa +4[@Zat\p!]b1+*Fa1eۥ"Y,v-,X "Jy^Faak1)GuU2_m + +o<_S~WG,\I =Dƫ>]|.GU] `_A{]:# T2#EMh  +H9+2lyxJU&TëWǑ@J`=1Qi"9OJyi5aY k.z A6o% +, @Ў;;]so;/QO?A鬱9f ?4;8 Q + "E" + U2 +UoNfoi^uM9H9.'99Qp;F# +:rԑzk+c#𣺮Fc|RC_t)b6NȈM՚Ѩ5ǡ> Q7X\b~ ? WȮJ)Vl퐘Dtl6 +=AQ^?cuoyo{LMc}撿.Sd4T^@D& JIJy {f3hoDCx|cڛZ,Zo^,%"j3m~j_)*] s2^* +y ǂeqꅫ0݂S% +DIa}Y6Asӳ\7s>I؄2M壝I!m'~.̏uBJvDR1oe{vIaT!Q$ +|y'VoD#V_ >|1xA5OM + ޵e`^UC +xϭf5U +h r#p?h&3cD#H# a~3Gð-ͰHJ̫Ոa#J1>XZuDJT +h5"< GV [M:_Pb{hQső6ı +hBBw"hZvno`'Y,υEY+lX`D7b]nL8v$9N"D, t8^.  +-W5bu97G=s˿HLeB߶PeiXٓ_v0RhK24 +ƯZʁpY v"ŔA*U&; IAM3a +![PHCm*>p3׳kOl˽r&,FO& +JPv$MdUrޡwG!l: t Pt/p\#y>rxNsTAN?k~miT!?׀gMؕ2= +I[{Pu3UK + K {M~&>95Ci`jx8S,Ss[M]%ȆWfqKskD ~Cdm0irVRns +Z-d1C Y+1az_Io sˈʄgi "Vث!-wfh 9p^va4~nKY#{*pW  +rW@O55;eGCK*c6-*0V0s +E~HhE1 zLP[?]ΠBM…:47db2 b2r;GSpXmiENeB yFDd&oq8K&ߊ +K~^󞉤a!&u=?gueP}" .O ey\a&QZ>maLDf1p&+_Mojc%9,?˂FQeãJ-vmT336q3nL}=XČ Z +n2%/Cw8Av +kcG H +*Ўv v?,+xpNg|P">b@_ ++حnY@W{s iT>-]7D,=QoPQ6kE|DoKqGsrtش:n#x(H.],=/ߨ1]|P';TY@D +Bܾ_~E?g9ݹ9?}{,^tEQ`@Eo <qTȓ+6JúaH,|u'M#x +_ Jk5OLjOgH\Mg+{m O"lS +-O *I^emmzu R&rt 5<6!'نՐϬ%C y`aH~(BڟP̣S~ +:|Af +1R&ԚA7#o!ܥBEgg2ˌ h((|AX* *s=^} 8FB b +M&C!DbQ`̀y>4K +FcPڷڤڧ7[#[n_e#ZՒShX (P& +h*"8N V&I~C@MKx +ЛӍ3˔PtNQ7%: [`"s  +z]ÀD{ѐj.]8:la}] +Q, +PC7`XO/;;OqO_O*6ӷ]g,G +#zIʁDme¤]|#\ϞQFrW|N 'xbQ0<"G+Ch +P6ts6Ck㣒ӈdEZ%zpߚPގ@i3➇G7}?@jf;j@%IbQ2U(Kf +%:x()JYLK Jk-?w`( +x^&& +h & +9aRQN +mQ54 ;RJS +Ys'rR:8 |e1lA@r.Vd[0|1tFjey#xH(5x$pxi6 #OO `-̿|a ڸ?ym|u2R֨?*?~MS9o7.{+wh +f +k2xU + p5|ק +`&%> +"Ϛ$VPϿn?kpPh: .mj਩ jlYI*ЊV" +߲Q0Wj +WygӍPգ+\ǽ g +כ9'YPٓG'g1` @<7|9c ]Q.9K%ܖɼ ؃ڞ,C)*nDƇHG +CSaF28EJ),82Y!VX9Q\.b.Y%,_qi9g//G۝-ioC`~IH^9|[H +;+'괜+u(Rc%u~ !oџ%C*_l_h2t +L9dY 8 "|DFŹg6C");{׉ei|]U:j> r,>&I6Md;vp, }5.HYOƟHmc2 k0 ԘG n<'T@[3st59 C 62h8,Et8= +TQR/h) <ג~x5@qNds>"}AkiGvz[xb~s +U*:6C*f6?u[_.4)PjjHiJT pKRoU@'DݨNgqOQpߕYA,_hi3';_^*+@x̷:|hV* K Sr}(rϬ ""If†+??OKSTz|OΣ?ǂolMz)wa`U#eeX@k~ +P9 ^WX +ҁ8'ԀSWd'q{h qPXu6oaQαN\.'i6 0@Z8σ2m$>^'_yPy4*恖qq_&i~H=erPFэCqh, +V^V,JTR@mGP +o}KNF#h:qG>HLaPxŠa@Q*j3P++_oΧղ,و@Zm{}Kp8Yت + h8M +,0~A]1"jo~q/ޞkZ+F2u f`dVր b4*UI SqsW$) -GQHs@lV?D2zI oV -Sf(OF"(lQ ""rID}^e)]TwUʮse?^@{KV/A1p k6ꊣ:b +ȹsHF1TˡMS?K+KTwW"!@ ! ! ?`M3ofޛ~Sݯ$UJ*UJU$6q4$iI2{& Isη~"Y 2̍{oD|{ϐ?,+FR+1 +{O0¤*}.pjOThaJX]3 B80v裓9Ìuiz a0(SZ>KR6xmֱԄp-3 4?fJ:9hK +ۆ=ZV?Vy^zh.5D)XqlgB{ HXn]Th +'Ȩۯ}~AVp( B0Ȝр5DD#*V{(8% +L `m6sXO$cmH1ZKJ"Z"MKr?,qT +u0/jG㜟ՀҫAM!!l?J_N>|1mOZNf|(i{^|Tղ2Ng"SLff@x*I r0iOko6guV&]x!?p~ſ;UCW=߳/W|/O=]0_o7nD~n6$veJߛxd<?kj /#A?fx/:" `Rzet'4L!}J&M8sC8PU/"ehq޻-6ܿUFUfsAbn/-@/t +?"\ )@Akߙ>T]5ՀE=Xqs$$B&GXh' V"ɐ 0Nɥ7ϘjRMB0M CR]gt^ +O>≄r?HCcp\ܓk[@g=[+a@3eWQw@9xщcLZ>!z9Yj]\L{_g"_ҊJX}# +VP-@Bl癍\^k[p.GFqSMְShY K5֐(,Hr`vp٘m@3)x4uHtyN + +Q'J90Eh gLO%}/ S5ayN_p9v[XڑJw %ćϓwg)Ug #@/'חݕ˕ X@ +]@+Y` +L8˨pzʺhC5֑fAw1R1teD +P-jeإn= ›?e +aFrPcOP +! [pW([)@ m(VPJ}+5_H>Bb%B 2RvAR6 +6)g3A`m`QN_Nl_k,2DOw#$ThB:?.n4ib&Vl!I.lWĽT,-&/8)58&tGs I%@O[xYRcf`r2;v&&P:GiyM%뎬?z+ . ',c g/Lk|Uy ]|O~{gRR0 4U^S;X +*n>{OR+F߯e +/`Hi7~sѯ"={lsSڜx}Wex~+jen,$PvqW zd>)j9*1qsM]PJ﬙40XLO1#ʥ)ӗ>ިGļcxOv xs !sW,ƿ|j^U|KOxi//o:?__z Ͻρ3>y/XODOTW]5 +xkA)cxQsAQ/"'?~r9.T*սP; s(J 98/U^Dt<!)")[pC7#DqOn"qIZ%WXegLc:xgRB} +}%+8KwyOꎦ!P +Yz/:DuX[![4(yQ +M>hi,`MoCtŸnS +-6.98ͣr{|B>US.4GnٗxA˾ -ܟz0`_K@2wn&Dž,\CCGT|+dD6A7hZ1Q>:"+麀v1Dm)m$W+D!`k%b͗}VLpL +o  C +<_H8G#QÑ$ܟ$?Lonj1=rHF*u-NeS{ U5ݰ5&Os+.ĴC*AMg؃:PP(Nmqb03%xlņoT+7/0#@Oj4<Γ8j?־$n^NIxYMtxj +ם0oyN|¡ +D&Ht)ɿqOm1Lx]jM׽V(0zc(~txVfe|PSC)F\LA5k +L93Ӹ4 +`8ηk`1Bmql).+hL6?Yo1CSB:=r=2Ag]CpZ>')67JHA{);i(ƶ;ZM*3UYfl y1dùT[ +Gkbd/v\2?}I_ \M;]OJ + $J?8Wm@ܿ +)@/dîJ&:4kE_ZR$C MsHEժVfm*IύI&5xg9aG.R0j-{8ϊQa%>n=@3ɉdI@kME|P#%Uk [bj dd0N{tS KeOf|yU0\VeW$b uW,ˡد-l ++Τ,B38Wjn&T +͘*$-t(iI_V}$  8$缀˂o A7 ?$ +W̓**OڿWO6N~[1uo{ ^#tMɳ$ +WpFWnm+$"$ +m| x4!S!Ɛt\z<j]=wywuDzW>?$R W8.ۀMxOl49l35 h`z4 + 4O_җ[" +t쫿:Q~Ň}r =naSO7}b Ck`9>OTOM$\]P.wa! lޯ Oak}QK~B-?NenB%-<pq=8dڹQ%7-oÃ@TLf Y}@mmC*|"a\"+d4\ :kwΆ'u1{?Sto>0<U]B<\Kf +C{짵|U{\B]Թ@NVȀ$W&!nH(кUMB TBsuLfe SI0ohj q:¿@ANP:,|XP+Ap4iNQ &QIz3V̯|;r Tl?&3u qJI׊ +f!ثmfwr1b3x{!OۯHPX 6B)#g U0&׵ = +F#V@JBɗM6~Z^a6ćZa06×;XjcXTl1S} oށ۾%w8!>̿_yA0Mϋ!)F6b8*asxB͏N<2Qi4/?}I_m)!5JLUb{MO$7ҳQ#y_Pև|g8 +Ǖ +tR0aOz4_ \O|C_̟|Vbv!%)|IOI%/xX0H;A5Km*AJ$KXZ$oC)8i_=mM~pK|D +CV>+)ֲDκDXX^{?-')# Adc01٧g)nT fc`Y"l<~igc, W $>vuU۷оF>q^ +{1˿3DO>ң&X'DϠƷ/M#).v[}&Πxq:"S~^%y:ӾJ +](TW3ke7"&[\*E1E4ZW И+JWP<>lcJLަ8TǓlﴸ~Z+fuPdT3Jml5jdz=939_ $ \{}790aa2O5_E9Bf1ĪmBZ.G󂓲ȕ°w:4%8@YʍkQsKSWm%X=z +7_ \ +$U=!/ +/'HhY D ` +Ywˀx;bZ'ߎ}+ g='s*Lp6_ ׈rwH_~3~E/:ˁ_v@5aiG@A + +W5f''}h ss +j5tOB`I _Dܔ!(h#$A F/ri_HM]տK*ȿIQ0^{~~/. Yr1Dh{"D4_*%;OH;?}I_i5ƥ  + chh;Pڂy;W'^@oH`CC@So@g2hӘ8uYƵBgM%j e2LQ'BPnWBn,mn4Q@㠾9h1)<ROB,k0@AqjCƖX$-P*goG_QUwC!GI?F>DݭM,.qfV5),~LACGJ摰3N'u!AE0>bW+:pD"o g,hwO&QX<VϩW-,תJ, \?iQh2Slk:HeU^k Pht-1~<`+7 d(~%$LN0=)Q$:wm`m!8D&]ZiK kSJAqR2 +zI`} 2Fѫ9E. a3xFX.śEDeE#óy"gH:GH`%鯌_]K_?m [T.}he j \(:+5?T4g4Ё><ӻ2# +R*c1 ?q;| ƽeW}GDǀc@ߩwP+HPj+JI)Lд͕G45Ľd\#8+צ}w1~r5w)]YXdf'u*QӢ80>eb_e5+B>[)5ڶ 1!΍FV7'F5h?i3,}j@5/ +~ M53w{7{;/Pi?f;= +4dƁ0INW{[ex=2c" J, +~YؤcCwf/+$jҟbB! KRw80^4VC`ZCa^-bU l !e+c14:P9c} .vMAwr W+X?F|U>OC%7'3@dU݌WǜK}P P(JM4xx\ /&0A,I Bi]F +GhJSj\Ef-NB1Fs5|K}&`nINȭHstLu!bPI]2@ZQBQh?g%sUǝYޜ7Q"~ZunM~Dii׃sC2pUw)*Z +ߟbMJ28/#mA:3 XL׾JQc.Y5I74 a_#'2k2[I&?C{ff*T~p L˄1E VK}N^% +H + +xCHtD169l+w(#bS咗ۻ|琏zǽ58gӷ 2EvI,jF,S$DUIJ)EjO.i4-c eh`7)>xP?i,XFp2>`Uy,r+ج."f&\d??x`Tӆm0ĩc9O 8 ar0~j!0,~ G#lSENfngqxN4NK eE&Ȭb3Bmw*G4YRMS,z4\`2NV + wp+zߕ n{.adOR֣vmPH%%R_ +(s&f9tjOOȘK52 j`0VQoZv;%BM`c[3-)IA7 t]ft)?8?HMba~\!f7>Y$gr2pfZ` 5U)$zQTZ0"-JARSr`IZ|j[iM{A_X Ï=tQxD)v^+/ +:a٪rFRL3< ^!B]"~?\ ůÑXt?}I_K/: +f -a +㞥I^E񁅕9d򿚻npX +/omOp:Γ-n û-qgc# iZcSR;(f e@xkAe 'cBmDqexɽSdUl,<k.ܨdR`Us`'k_{?4GRJu@a0Ae\©SdƉ4S +N`yLZIF]hm*wMiP&`"2\4 .3FsisPq t)qd\`Yfc3.yiA5eN`o*2F3R&^yZ<䟅z['=;of(#Hs2Zs4-#oZ!0ћXzc |T2z#p$c8M>ɐ$ȉk3?KreQٲ-(@.Ǥsh?fTCe[I@ +*3^a҂iHBvG"^Kh*wO^ǐIcw rwW4|WB؞ތW?Lx4&PJ.1pwcb1xgvHE2K~3DzieΣ`CYhJ +5`2-0wo# A%1 E kPctcm{0ܑR.\8RÌSȮ0Blu3"BĆD~to#FBm!c#mh;1a/(R;x" ;pu&J0X^fo?# 5 G)?24-f#o+q31:8fh +m~@A0sjҠFcv+`$e1Ԉ"*S"Z,A~"^42h(̟ojwG#l@>Aq{d\8ںDBo O. ?g @qa@2 b 4tB^ +,9A$D0%,[ &`$@%on# _?B -㠐əD +?_M Ha z=<~U/ܧ 4PWwt O΄-'ͦLPJ +F6USg?3r!OO|$GK[,V.lVkبmh +q+qAƙ +?ߠPۿ32 ;MvsH(LoUhbH")t'CFMb vZZÜ$0>` +{#`_˧ek>3Z& +$LLSP5ZWUr +0-a)^ZʙR~Jdru2{d3cn:# i#wtqدJD CI +Q8K}\@ ,:Ys,xDZ +y)e 0ߖC +P'C "Wg~%_>?: H*}0q;"Uwi`d_)8$B3> #bh]OL7Oz{뾰Ӧ{ivഉ* CJtBA?_VpHaJс/eQX=1J+K4ztMf(Ort^p|Mq]љZ.PIVm>kYߡă>X@vGpWj xAyA5~59ϋruF~LHuN*kile6B48,#Iy塻4  ߌ#zSJӅ*aaI\^(jtUR)H"Uޯ+F/tW4KX u{GNryn\ aYZDmapE1ƮM$l(@HD~@3LxX@h/1^SEsI{+;L{ۦVj-qԭ2vFꧤ\t rg_kQ&q,9B~Eo{)?*Q@AuHV=Jyn'Q`3.Bא5ěF> +`+?{T>*x +V[:H snIZaC>=]0ۜ'?z]m; OOK w=[Maz=XZjܳmW[H"V45^!_׸ exZhyi(@qXQN,)mq^ +!YM!*vm +H_(h[vMu +Mz:-92 +" Z W.Ŀ'P~2mlK_{{,w=<9k~mRxQ + FT=$/gWȖ=hZB,ÿ"V/ K"Xj):@$?5D!D D[lAk mB}.+8WQ挋M0?b#6T O$mN3 ƛLRo׬ ]K&4㑠a(}\Q]DɅ{i3 +Ì 0Y4nE4Q"K +\2ԣa/k IYcsiI:*>?U+^abs/DHL[W +;݉?Ogi`\pu +\-/N~+m dݔx/AD&燰ʋ\goePk:fX3i@f#Hm#SjZIv>ߴ'M7 gkfǴ# F'lc?y'9_ tT6ӟ0@'ˤ:O^`x,=F3sm2]:NzAY,`@JC/f +ӮLϳگcƿ_wFI ?4NlIskdlY| +0/B}˖ +gO%\)oDq}w?S +|~s齊y +rH9KZ`iky)[2l:D9!(@&N +5c=?/ ,t(~:s^ںk3F0 d%FQSԾN!ZFH2xu[bh bv31b xP!-ӼA%| M @zvuV?W%s\ -j85wHqZ +IsI;P Bc9Ilۧ(k4%ـZF&ϼUH +'o +:i;X^aTasNU v~8?MH ^@m﮸Tdњ;Wg`# ߫ +J C! Ksn7iT ce?6GȪtg`!:'jj;9|jܟkg JP;:K.u kT1.b}]an'Q{HoL{&UlϼJDEĿu', + +{eCV=#"FZˇQ#@T `Bj[D\)6>k| || +M鋂MB  *yԽ'igYR{5("J2a.J^.?Hg ӭtԖ|[E𦢜9 $ ,Ҁ B0]&_^M;JUHHTb0Grՙߊ#e@$ FGM XJnω?FlSc+v`O@$'2K%tascD +a Moj,E+?**Ib~S&HT;^[i8 +U3w=!Y*`&)l2ƆҨqt?8]@:Ve?az %d;gKM`p` _h^&i=Y Çd?$-7H4*s,ZmBɰY]a=?#䏛]9n~x^xq맄qp ,D)Bx花ƂQPFYb2?{`а~AhY_q7IoFB]Fs-1&njxxJU,71!oRKbme\gb(&USRF1w(1^y=j~? ++|Aڪ>U1)[9W)xqxftoF{NhwZLJp +>VFX oVj0_V?,IgvY4cgףRIYOKB=/n/q\(>oف7fkJ5Ẅo}>7a?@Q +ϙswiD795,e\d;j~F~;f+B{K>UF_:ӵ2Q))8'٩""|yKNo9{eA׈qwՊ(g?f *Ӯsr +Z'nABX%GE>\"4$dT _6a,7({L^?÷-ێC;H }, ++5 D3FiԸn2hkgx +T勞sZb/o6K xl%~gXPZ@Bw?:\&S^ _]tr +‰/b_2 +x2lj񜫞.gfR{3A%cI(YxPn>`K~zC +⣶pᔜBsZ{Ҏ^ÝVq.<^Ŷꘪ3V5UX.~_d' Ζ#,̟oLx~sx`;J!^5Vg)K*zFΫ +Ἵe:;?7Z߸$ ++b$Y u?Z +肙$LIwl +/J9GoNv-`OiBAhBv,IX nūۈ͒c1t +,f& +*Г4hiy7 X1nRdMrCm ?S'eER^ +?B +v+|-riNQJaUҴOu%ٴmN'(BguG9NOd 0cKh᧺\=~/='~iuaV5=R=UJ5V2Pw2|B8 &}:#ru"P_I}?Ë)R4<*`V邛6 +K?#D%<~}mu~g=^R#oZf-柧*ޔv +fGm}J +Ӭ0=?F@.KqpNa;m։_ojS VVmVCUc]oqh!s+= fSV5%CVBɠ=UNWrdtC1MT?]'bOV?S2P^CՁw}peN " Gx8 +.4U.Du+_kT@+Y`[ =d/wFw;y +z@]jQf{X}6뮔 +&&+QG8=`!4y^edoBT bQIp(:~{PxEkk7I%z+/sq8+r0>7r,je'-:귳Mp_wxex>v'7 A +6}눁xin)cI8 kZv닭}+Kp1 3 +7 k%8rN즠 +,atڠC_~t_~LOh $vD2'6BCJ2O;Qt# +c䀖gxq^7al$ =9ݝc9Jm:AJZYY43"w+)0z2SAPI0HZtż] =cC|g\505_y7U/z8M1`(24pS + .#ifKm=k 7XdϼZB m0{= +F<=r?PJ +<[*Aox%ڃj=H˅ `h6CԵ'-a +mNӨh2@h +% +Sh3^ |=޳VE%xt=n-o<JQ@1A5.zVyx]J0P<}v`! + A + ES5ʙ饍oB +%Q5WL> jo<(YdC}djP{BO8>l]VbUPӝg͞|Dc!4E-UL]bβA^^kS$w^:-h'cA#p_]P +8XG*gψ΃F +[ +^w@ '~?KʥǶ2. ΡuSqB'}>o} i\rw,Q7zlyJh1Nߴf<ۯ u d +Nn sp<#l[uFvh۬{"DWۥgJQ@|X=IT k?ZޑMtLz˕"P67{x `)C]z,t~褷0Dg[)m<dn|!b%J +3l릋6'LQ5Bcҳ<|0q9J_rC:!!,*?A" @\vTM&enwoQ޳E>2Go)@:T`Z8l$Av4!k%  =mRy-V?*H).8+WlTXU +Ыv;#h#0Xبd: 8"f$>DNN=w5' +NN;Qh3XS8xaV)L͆R#~$8걯42R)^aA)B`!G`qmuwHJW̟"nEGy*"#EVu*@P4W$5 n#77{ "g PQfR8ZBݻ_GK_?U[v6@~'Y. '|VN{\_[+ + J6{$ +MH) +IT2W " ~_}:/7Uo4ܾ4jr&hLVHy6+SD(A: FKfBLgY`CŒ_]x>3ͽr*ģO~;;?k`^BZX,:KNX}U W%<&T_'lJ.1IT۹LWB |x/y0G!˓g+߇;p2Gbi-5 W?, 5(0!ꐻ=&==Fz9 |5?QS$g#Cd~\44"nMS{Z90 YbZj**,jzp.xZ)`.{ ]{=>{sX17Fd[Ts+˕y|K?ⰝR'h~&q1:m҆}$.zGL@,(vf}'U':OjNg{ٜ~a'BZpªDsŔx;bkk-Io 5qէ6)?UPW٫֍I2[>فH5~d8g^Y M!>[~w-s3 %,1@.jOp`2 +^TxQ/vz"F8F..|&@`A_0>eϕ*ΔwhƠQbGՁ8?kdyge4GS% +GF@ BcY:/CV v^ +_B*i%A?dI{mz{n;. t7̌H +E#bU3AE)j' q f@P +_-^% `ɤSU lUc&7)6*SVCM6TLLW22SWycAEi`]`q/OInjdJ]K/>ya6YcI4o_8`mr\%2[8.YtT,W&@ r"6h_PZmہ ϫ A;!hCCt }X?S"'<*#,jH;k1i&L191I#+ +пeo0KZ +PϐLXzY_߄$]m& 2SOSAfGڌWB9kOfiW) +k\Jh̙,L[ +M䳠-tHҽ0kQq"=Pvn=im <2O$㆟T98 +"J +EAnp=n%Nh^Ӧ^֘Z @#N'=o:귿< m4Hy @ +)0\\*C.M +w1:>qlxumPD >*Ⅿ\^Do +tsKpddN*fuI;'m*iwqs{ ]}#w"_FBΊ\•8^C}D6u_yT?GKSN~Zc*Gq.ơ</R=Z(O?QݧnX,ܱd;x5?>?}+zs|ʰ% +HٍΕyֆ +r;kd9Me𢡊>L- 6ZU$^>i(I +qy!lyޘ27Yh`d@ +ǯJ kf~0EBvܠwX{/Dl)!Y*QLmqEUY8&/0Tė +|t RbGԩf]$d\ %ҜX(C ?S@mϒw>=5݈t>r7_V oq -P^so\;PgvfZ}DZ'LW|}hKCJA +&F$n,HiwS_fZ rbX&Rg4 P|HfݺOӭ-ʜ 4ɐ?{XYhrÔaJP1\,.Pް2d8Ho +;r%# $ +QFrHQo[U_N\?#T +'(v*T9,8Q^ V#/ +_U_s҃6e!"gxs;RVIz +Se$;؉\+Wv6Ư~"iK=dL1 +y< +Hhu]V +_W$ p$ԋKe ^~(R`4X?N{&tP1x!X*asۛUeu$5=g㺸ᤳMSPZBQ#8G^*/cgG6 -QPڿ% N+cje2l7賓|R_;ʺKlrł|@%<GPY!~<&OZ_Cdb>"Zauj\Q/ +c"a(b5OFC# %ֆ5胚< AΩ8N]'X+%U^*=< 6Ӝ^ZRf)URm[wv PS3\ +D#3x@ +IFuP0^2,~4fp ]0/b/$LIf/vMas^lv"wr~+ֽ +s`zi~q0Ս/}ijokͅzTXep (}jRɨ>;8R1QyؑxG?1 9}dQR O! +NJ6b4(@kTG +>xV:Hmc}ލOO~~o 7L8,ոGrqbKթaD7ΒX,RHp\b8E CiCy#\h7>yi5IReb Xò ;&9kϋ Sx_ld1R;Yh{^'J\ (m( ۚ9bOأÿOj1Qi$ y=l)IzgH1C&|,PnaiSГG"lv;Ki|w'^6QH?</*@7Hz#j +-Jv~CG(m*`Of#xzaTy>feәruu +VV6D3ә) f?8g7]tQ6I<3rCY9>h?O6a`Q\h/]}`z&azh$ +5bb7 +P,7mс*Ub%lrƂ_6Ixdďf+gX B@!5u4(I +g +RnO/H wae\`+r.IJ +)("%Gۢp `u}F /Jے?L^W46Asq^aO"FPbrHWGeBpuBf]GŰ +mngt'6v9 Cpv3?{@[R9]¶ +#v?3`-&sm2^WgJm +V5`^[ N;ëI֊S>mPa]F?=G>1bKc~?7z~|~6n}hbo>>g@`*2(ϯ7s歴^7V` \𭑁Sp:u +*=\eki-rY&(~Y3PF e?^vǎXEg85<ã! +O@rph!sw;EoV rpK +x^F@w%񽺾iˏJ2d@.Hҿwf(>%`_F+Jcjpw{Z 7efPOGB/W +IT|.}_4 OYVFe@3yRaD|k~5u&L=#βMУ +@E{K << [-CM }?My b3Q֥<軔#pgZ|w'5zc_C H^dz%@3@|3UNgS* $_G(R7`{|[0ؐ?oE)}yAyYfRPFo +T +(43|\rDh~!*zݵIE@>jrxVDrGR#D"%a@l(A%*bJaktR. +gch>wkѢ̔ =r`BCc}Jg5 +1^d 椰B=KZY?&sdIVlGS + x +=T2\\ D +i9Gq&8!skւqap! LA _a$L'7: $'gzU +?ޗ :5]6E Y) Lg7?\'Mrt@usy>P2M@\waA  Q:-,dd~$#"h- +N(@h?y +Q6n%rWMͤK#f J4pS$߱vpA Ce:46o?1u}{N8.E +kӕR낲JE^֝OnU +97٤|ޮ`鬞+B54#h"#e ͡ +3']FKs ~ZL0xs4W=bb+vҫ>p"[3WD˩A +1FKlC)Yp@=s7X-?8 h(4w9rbݲ6܄7LB*>vS< +N jFV#9^s&n3āe7Ѱ} +a$E-w'؟\cT{F3@ Ŀw`6_d˃ϭ8X@mVl/ 1Qc ߧ(:zmvPw ϗ?2q0ߊ'.oq6 @a( ,6ORd~]lo_ۿʍ'Zp!]bzղ'wynJdM>FP-nHL"2EJUreAQ/*wh@,cJ_ryF_ +, <r;#y2:Nx5 +Ϻ +#N~Cxf~݈UtB?+ sg0Wc}(f`\d/m+[KA\BL`ц@O5.^hnFr]!2<.@fbD69:#'7 $y݈m@vje~rxQ˼#QL8R>u>OleFgё0\d q +lq_NBeNryf9&|. +g,Syk +[s2po67);V.q +.ܜ/ivY9l&,x[)y`d4S{mcW2@s9N<(uL _ta̷W"ղ9!=ˁN ? +6r,fD<O#f{<@&<3!,5l.+?~Z%^ +OD #R +` c6C7 +.2eb5x?pܘ9i;܋d\KYml"጑?3xg&s*b4)tYAޚ9J# Y7EB˅W)k ?,z?~3_nߛt^n8HG@zX-w}|?ۭ=K+M\`dq{ +c[xD8Kk%`zTź + g_g_~ih*ZSCL:;(V^?MRX9 +-% //j-R˜;wg  ~&wA&< bg qX Pt a6z08E널 !FI>$W}’[,j7N{?b ZvgIqG[Iy}FZ]D\zOwi8IV޷&vN&sx-.C/Eeښ*iҎKfr3Q}Ht:Uq~Y-DvG@Taw?ue22xCw{8Oɠ`d`C@e*" ShyFUmcM`y9SM>ٜzQ +P +UP7$67 "VȆviY) +);y$P'ylOvd|CT>f`ĥyGc]ab^o +|9O#xw!pqW.Wհ"-0ܜܐVJ}d]}t&`ɬϙ 5ۤA_<#KUKSۿ1ٻJ>Cȼ +/崜'+k75؁IBpùB|}xVY{a<ҦIm +d*ZZvHZΨOH &<^ZC+ε +B t t<  +ec+S4u?[-^VE Hb>2xI?邉M6jТNV*H90dhCn>l66`Z9@%@S`߽4.x$Be\`j~|7) +6I݁vQdO\e +=gTX:}fhiV,zGHKG# +vk(XqڃsE%!!`J +mOg?XyBG/DyJ0rʂ꫟˱wrin8E;`mqn$e Mp'WQny hχJj+u\} &BI&#N $s/n>Zkzހ;WĊ+vd$;Ļ䚎DP,liߧ{`Zdʼn_E6K{ +";#Ұ{".%A|>'*P%y͵we>Έ@d\ ,ϳr)TI8!iΓ$D[^Vpl̰VvqAdcͮR*'|<1-?l G +9?HKlp?:\8Ё*UӁO 4$oڑ"OV,KdYyu;Ҕg(ӇLmՑಫVSv S(4S`c`Q|uPg@"O#?x)j؞A 8ٍ k8B.V(=Gg~{+'OVF۝uu*^ r1OsŅW򏫌ZyV{YG'VJj3`i'R& =ڭfZut[VX/MMH;Bp✗5z#-Xy|=|Q08ej%Q5@ 3`l7Lx2{0{/Vݚ}`s}+_"mRGj@'t}|? Z?ߘѺ>Q}@ `@ze|UU6fN +{ +7&3(x7gsف^BR”jnU +@폜3l !_qJL4T.Kd_`{>ϋc(Qcqs`#E9Λ9!|O"7rHSB)} ;e +zF?s% +˞l oPg"P=&sק=źT|S9)}nwdgDvwA:6Eo-2;q~ǼxOωff?r8к^.W:C.W5W +#ֈR{"-V9B#ޮ# xI>>Avcχ@{NID_EpHADKS:o j Om ڄ* +XrDwJ[/J!GN>8[`OXKʋGhi|ˍR057-14)1iVhl ]%iF&<6iEW<\Hy 0G_էiz hZcOByiD= DqA{-e-+u4-鷇eK7m 3h6ɟHag*E#, HnnW{%]{<9TsWcFD37軧D 9IXV!`wTG!:ụʘbOhN& v,)az12x.$0 +ڂ&v2|&EDh1N/ 㞲DOZxU\3:ܦHIAPJńʣ +v巟=Oxz(g/|d@{k?*7б%AcI g)g:4u.SNqwnV{?kZ;>NF@%,YPG yo+h7?ExAr^@crj]j!W[8X@\KZ=Xk%@nח+hS +ռyڧn + +F@ +K*XKCIr"!nI|:\`(pF9⸁TCW.Sp7[s{| &2Yi+ꫢ圕پ/AwGOeJiunEA, JovyMj?Ka9ݼq4_OM\j@̗7- z40\Ȅ4;viQtx)kUK}hށ<_#tĘo‡_Zo]D5/6\XX߰ʏPȔ +' ]}~aݨl ; yM9L k8LVox0g +Nhow<ΠW!jVga؍— +`5%3T)k|;-[q-ߺMC +8?QdOS*/ JOz5iHE:&E$%]ז +>>J +֚=DՃ$Х|R`ZM +8`"U? d-/80 "flS.h?C{2 4N} 64]Xߤ%{á7l\ -ŞmE7JHgnHdb\D }6)Z˃\τ8r:4_qw|1i1=J3 +@ PzݙRB8,e#3, SSV}R\ܣ`\J @t c1,w35>rB +*FN0Iq-jhb:|zOeo!f>=@#BLbϬ1˃d>D_ +a +'*H +>YESy .h}Khn?`!]t\y24o)=;+qV[*56E?(aT#Ȇjoyqac^73:9h*wѐMYiK"T!,},L)#hahSP7)7˪iƹ忡a{V]ϖœDjpN0-ZpK/#I:qI#&SRP&3WMpg"oTDg'rXmFf1Ɓf<\a,툷<3"('=eNBk9m+lc>0\5Lϟی$ZoHo +bI(r2KuõvoUjBS3_=}{ݙ %NG4f. +c +jmhη s;"= j-o_ivJ;Gس$){omYYwAA@DP4y$KDF  PSu* + + +hiD:؀m2>$w{ǀk}Y{^3|9tC%0>\BXz#9Kq>飖39_O'#_vM:U.8/L o +ڟEFk_d(: +Wt >Y><!!4me+e g{"fs$ +oSh2Ex) EȬ-^ݭVR{Z8C"C/(#"JLMdp 8Tr]PeeSb* ++EkQA0gaҁlT.;>S8E],+>[\Eʩ,͔{ܱLA8nߟ$T,<>DQ&*sq"SnYl2+ 4|UhGYro2ڶ?zC#O؋l^ ޚ?` +Q̟{H?JƞRgS?}۟|X4jq_!ai?]?1p8v>Woo+4脘09U1ݎ +M7.U^f9rUIdʷ\ScsF_>J|6m_8jrHÓG1wK |~c2=^tG@djoߖWpD]-v4b?|~&#i=TGQyV4YoM +VUL|"Y`QGY\Dg)y' g3F)w|"GjOty6Ehgt,&Gۮ -oL0~S`kS-{&u)"t)I]]e dJ#zDHӷ B']=*ŏ\=% +[=2@/1x b@>VjD QxB&c5eQSj_``S@e ^]C;̟az +܂_⏝0Gӊl< [.@wF2w\+iBsHvTs Pް5(@; j*]'dGDI +6 Hx,ˍ_O9 נ 5`83f2#4(AߓB't`cfme)&ҩ+9>/M"7c!;R׭!v`id%)'w\ޚIyGHeD OO9)!<|=Sz4K\ajT^:+pG wuA#I&cKKfUX?]$b  z'cӘ|\TN*:QyMn3 +hH߃o_wv/#c\yTXNN]L7/~v(t` }$@8(ONT ĭj8;g{F1ZC +3AՋW=TWlۍ ^A +ϊg +\֙ڷVJ +>DxL>΍?"F$@բ|]* + So: .:RSyd}nH04&׷h@ p_91y~UMآ%O !:VS GLN5tpbNq폫 gK]xzɨjd4=aW*/+CEɈn#}qNSY4\/ +p_a^iK +{v,L;?@(2C3NxF܅Ɇo!gq\Tk\k%M)~c+ t T7x7 +lK a5 `#Uf00$I?5#804(zݖY!b7yw_<]Vs 4?d@ G?0&KYl⎣LJA,o.͇?ٕ_5V *}LS'=wD@9EJHm!>- +:;s_Usq*]ȃ#elj 24KWvqQ7ɈR7U0Ƃ_ Uv(Ta{ʟ6k|FCW'nElkYo!7˝.8>v/VۭE\#}q1$?'㞲F@(6%;tcJ^<ۿO 9_QtDïx'eJ6aL楺`a~"7Ejc7`gmK`O_:VWol! gTyAG +)pLBJ\ +CѡKN}%@h6ZLG5'%(g@*-2"pz Žiy~?rtĞX[ɦ&(UĤjM3C0 +r8J!H'QnG<č { +2yZji-8 0ϞlSלZ3UtCĶ {QdR-&&w ʸ] +tkgRP8>z h^nkqIZw?G +7jm]pElH@{={O?eßAI%_@6yƑdo$t r?7k?`|5٣{s\oעr^H.g//%/߀NqK23 +~0Z;bv8Q S;BϷw Qq +Ipй2OߓTLqۇͤ#LirCi˛[]ج9txs1 3)'?/m\K"TGUO$OM9xk! +(X]QR2RrFO㔊$Tq6 u>˫e3ϭ亝U,XMUn8h3 % -iJUq>@;>i*޷ +VZP׆`3(O3Y`tA7u0ԋ@PAVCmh"nMT#}iY *6 s#AE-V) +O!~sn M[Pd ?,ńB[Ҕ7ǐ {Ħ1LA3ٔuj5n&=P,&~)sjzN̯U?K-+ +d$ ò@Y!nݍťx>f[@;t!j46$ +3~/>5e +1/C +^ IИCt-AcR3¾90x"BޗOɚJNT9 vbj#4,>c>/_,<* ~xYWB$owfLl3%qqN,:PX>&۱SŸa nmߩV2.n4$ + p6kOR bFOhJИpF$"JQ.?׽vx]׃\ 6S0 B@f0nqQb^J)Нv~u_<ѼE;ho֜315To|8ۧ {Czsj{=Ip%ih&ѭ\(4" +ɱ&N~ +ؒT|0Q_*53U_  "3'!T)c8*@joL?O2Ks]Rk.pNb7OlQۦIDŽq2TcpU N!)U%S"2)<:칖RI5<煱wS> po'#}:𥖅!?8da?{*1y!X=p)^YAս[ɰT6ND$Pޞ9hʯ4׀f={RAdI f'F) XU5$H w߂ia*F} +'[K-1hwA>)P_}_4Oc8o4{Ad}ZFt2$+z +/8 +'⃧G|3CH&lu<˖x~aô)L7SS21-/ꎑO4"  3g^ فĿ&Y n1bϭufdX5b\ןr".(20o7 +XUgaE_uaXeJ &?,tU@ѾҊq9f#eVyGԆէGSs$|} ()L:>Y-u8Bqfk2eM} mnvA +{}ڢiW:pfl4@tC*NsBƹ +&W5?=:nQXp5Yfks5Ĉ|^OUt03r {6PWJճt51L;Z`rgF +5S]f@#*a2~{s +8FқӮy$zU;u{azYMufߛ._Zpr`j2ۃY &>>+q$P y +(^Iuꇏ>eAz F K ƻk{7c|[^ReFZ_GW x-?g W=jʖ+{PXTFPW~Q4Td0 Fӱh{33fёeQj?z&0#+D +s jX"QWejdVr` C +>jsՆ-nEvt2 ^UYNp,h]xsPuUdnШ!u//1CG+y'ADO΍ˍY@|^ }rl,dMƍ߮,;[UG+V2:NGmn7uVv Ȧ0ތEh0 \aj~{Wn2H)ګc8f-CDŦU":}Eco +:m&$=*Sdm9F\H/О u9 rsJ5Ԙ@Im&=TTE~+kTϺW62Sz^`63_a3yP#WQsq}v2t녀 E5Xu_ÄNsNM0N.'# Zt;8?c >#'}EˣGSC![ +*[[U~n ^m?9ׄPf?|KG,q+5kZ⢠_%cyäCߞ73cwנB>d&o&4Dq!fABc"v.flě|]eIQ, .P+$-rf'(#BxWiCqIsA-&/nLЉBqSI8`~"p_|Yq~Y|R}6V]@f$ J90`9{'lSbR| ++7Rk:IY(u6i'sR+(̒ +f~x 7[˒+h.NM+In3+PGxxa^{uTM + ~ +@uOϩ#0%Tg'P zF\T I85 %H +[\2Ahu$е!86E9@{ AWļ( %Uʁ-!6A&ʓ>#|Z F !EL|m KLQ}H%Xc%@ASp~Agywg#.(hPvN8.hܻ҃ +'FNM|o#ԇ ֭}Aő%EϚgUs$\wJcB{{\HT #hjdGMj%uY*~v]A^%pM dOюO6︃ +O>K.CIhosj +jfuJP +ɫF +]׳_X]p FKg]x!w:aq +0$8E~ +aYV-q2Q'P?Y6~_ЙfM +&Ap 2[~L~*&\i^TCQaiy uy(1tWV7 sէ_02oUӍ;?.vIVa=a +'CS9"rTG^ +6gJbѯHa .^O#?7o + #by;,L +OzQBMU b"9rȻ[ay(FL: +em6@)n#?kQ}GwHN#hyw0%uNW<.C+YV] 1✿r΄3ljFn7oyS C@eRbYh`\1ψR +47Ih1TR0*lc)v:Zt"Q֌ +P(={]`~7^ЋA2-f"=ss>-*k`F@YJ_&:=x>{lNˆ WY5FXgLЖP*o `b^>&< ˕B9w g{% ǂ: +/[{4ΙӃ{E77 &k`_;Yup-j`y.agtsQ Ӏ%5,BZB +HCǢb[_~q)y»_0< ++߮!_&ِP:\;ۂ=iع@ۚ`/71?6J' +ߙ#h)ɔ{_I>i_pi(@yY/ͿA@Z*🳀3g~,bzBOMP$]DRԶi͌Ԇql+g `]'J$,I |:+`X!PkrKXRDއOϿaP:D^͹fwzN| >$kr||mRzhHhŗÃtDbĞN۔!T5&Lp!J?h&)@hQ}۲IG2E7"Xu{XZsh!O4 +N6]\U\D%MVF9͸'4>',{Ѽp+ڒ:&mfGnt + Ys>Pr&۾4i"\*E6rU\]ܼ(ߨ-;MI~E|5aK2KAn#Ӫ|.t +_BQ +k;᧱tJX +Ĩaƒ<>;پY+N+c +F +j(^b!W/UXzu +Ip4L5h7? )2p_ +hBu4.Oƍr܃#S O8 +4b +pjTm<V\#-֠<;BPF UfKt|` +d[~ZO;*#.[kr,GӃMɱvE`nu1l[/4֗PK" l1`{o=q +44 +Sȃ*` p +%IJ֓Z9._el6Mv7wYH>C H`)d + iviKbלndZc=}C. +O.l5YٳiȽZQ@jnʞ`98 &jgVg)9ZNha۱ +YC~SՉD\ՒrV|.?Y߇ +`DI5NEЄj [5 +[Z +yI0@ +Ef+Կ Q > |A_n~H&?ulg +|q!t5;扚d ]x6)~p4-fS~N7#i^>ӆ??}}OڍOS(fA!'B o) γG1;&)Xv7& (tBh! "Ezo +o.H&2E&Cl9+ih`s w ,taFhpBy9|`W4;CfGogA% +~T)@y֯Fʷ6rHA>0ynA"k3@"h`Jݫ(u/׺s 5Jl} ?uÙ#] {W؃u7u{^uܞV'Dq ?>lj=Ë4u/ ! PV#<8< h=ȒL rUG@_ʙ}> G/iή?X5/R^F?.g~棞ZT9*{є +j, +>|9| |<-{>u + +ͼ}DG)SWZQ'{16>;6N\t bSOfAPsT %jr +B`iIoY¼i)^MЯN->t݈ɑ[,ҖHJ^zďB0$xxLcC@*O--``ȳ~6/Ў/w'@eG $)Vsi1\=N{ ~& G:veJ\rSJmALLAׂ#]@K.]pnSG.ңS- Uǵ6 +8SM]Y<& N9<Oh@/6 o Q"V7٭̬F68s(tPC9[zs`^-`xnCra?`e#pK +w\X]jMb +ЛfJ#S +KS]wlWҸj'|⬖}ƞk0vJ^ +$Ri.[g7γ[%S_y(uI%TL +[|Rot`r3)(%*9u_Yˠ`8õO.KMN~9!`549|*`/JÏwW 1us<;5Px`O2lE!#³@0<72R &#y/yCP#}iP'lpIA,_qq=ꋒg ?p5͂!kCwv=(%NV[9 e Y +@yA +Ѡ p"Ј2wJx!pfӥFw2>e__y֯ƀGCŊyOT gPWO͓֔.K@#^GF`@2΄M ,hl9U=jȫ0#o-8yp + e;'qAjP(^W%Tý5x;ƌJ\ѻ+P߷d8mtFˇVr/&(7S\w؅:W\Pÿҧ$fHs*WFOw'"OxO r_ +~J~𙧾[8ğ1ٍ:?牆Nљej#YLpΌp.Y Rv +(j7 +cFY5g׀_>.ҏ.[zfO((}Czqdž*`9 ΄%M*b|;n`[̥b_qkⵌbܤ 8Z[Z7қ3m3pF Fmk'i>֌o,[L+DϷ&0A<3|FsZy֯?o[iؑU:Am9B>0=SDLIjRA,?FkF!zxX^<9)9!Е*=.=:D/'i@@K| .x_HL!MF @v +3|? #*I ?#1)Ct5g a%__5i|b 02ްP֠*@[:"FR`r6 +83 +RP + , 煹`E K4 +/J\+& +jR}~^UMRPm kW"PB{|j-ڟyg"@̞dܱ㦑a[A:&I2|R9P<8)`,ǐֶ8bصiE)RmA)VY +ku t?WڈW^X@9-%s|EuY?3ycT@p'gzԂg9s Qf3W~O'&}hTtAUkP(V\G5veQ4i +W?i ߌ*&Q%bX䍷W39meW( +#l?~GD ' 3hQ@c4[D}5<B}>ς߃y_P>6 +Tsrx4h/GvWeJN.9Y>Z㪭0Z'n*Px6 Hͫ?q<yXh-w\kzW hebk <UˢsB4Mmbd p4ڬ'w?h^Մ?\cryBm4WH:(1 +8a;-v`Oy5(1.;v# fL UڿvՀ):- O3DNv߿F*'e&#&* ~~t jZ RO/Uio +D>s~>2 M+ZB <`w״p 0 " ^Etzk7O=j%"xl?0:#UH|J2tP庩?銜%l@}Š~r7]ȫ+hʱxkQ}SZ\Nn"=Q_ g1@/(FTm=:s%С?(Exu30X lPeM Ze5A_>kٻXk> +٣Zd +;@8ϒX``|sQbMZYd?h>qU] WV?f^B+uRNъ:pr/-0?pWŠܿAU:܎#@z vabŽ])@7/})C,ڠ1$mH +b\ADϸ`2Kc +uIhSÿotvz^B_kS^yP/l +%U*>7b,=|J*qP\_dcaKq49ۧՊ!/yD +!#L]} ?zM7\w҉ǯǷ~pδtYpKOc?X`:+<+*\,`2?oȳ~6/nG:rR^x9>K]xc]zO.Gu䦛nzA׳tUa[ncn'_xd. +A1~/佡 }h7^0} 1/u ?]du +qJK$ӡpWO5~Ѽp !K@ >^uY +Ef)AAH:Ԁ'x~#?9kN~SFGn<Ywa;qN/8=;c_ +>̓>?*T.x$0h\?e(Ӹ{G;pF8JݘAbt"u3`j$'I-A1ᇪ aw$ +>{5(8TWR1tV im195|DWy֯ƀI̡jCWO;{ ;kzzQc҇ +<9M +R>Qt.h361S^+$Th 0VKntPم!*!6܂-O]aZ%uY +_5ʖ{:xP}ӽ$ RJ֠L" d۞]9a]7x9ß^3ݻ_{ɃZ_ȻvCw;6S;|Cw@. V@Ưƞ8v&Q@{y6exxiXp>i~X +~Xz&o% +&j r-5sW l?~eڴ6{Y?HS߅$q 'mU7}OJ + bҾj7ڪr`JpM +cVo)oGhPw*!c-N6WYuT?Oc,ͳggXL%3se,vO kB:_a73oÆ] B˹wV?|a,})%/mXfz9/~fuieL.P\0 Sh'F +`re2 &Jgsá`]3uF S]W(pn1lI^bTB}StunS4T_j'oz#8X@%n`+ +{edP Mڲ`0e˨Vp5_ g)gyJU pFVϿ*# +a7>}Mgku~v`̼<+_r&u9kĞϭrn>3Z _zk pf$F|Vǂ.%m_~>_ AduA,sߧ +#|?&8XD/P @)r -LUP{&= +\%TLBom+܄ۍ([AƲiW0Z 9ᬲBJ;f3`@w<_rFϝUيXR8Ӈ'ĞB]-OSR&&ؠg^ +b#r)m4曥Sz#NTfH1Y=v`??Oakr.G'j\QC O2m*% L\zaHΚ5 +CuHڼ7f,eo !qpQxCx ϠMMsw'̗ջQ8&?.(/US ^VT +_Ru|k|Ai-k `)S !j\?FR8ۗМOM>g&mjC&nuU[s#1ON?4S( |.XW/  ϮgHDM7J}1g?{); +@~Ohίa>egxJ%q +hF +z&9X D6KW/w^KK\S_C*#KpZrnkxacW^z~kF +M %'} +@K40 +$$5̵%ىuHB*]˳Ѿ+u +>Fh #+iDz:MR*?#{O \& +i_ E_R-Q~.@rIXnx`Fɬi#xet.!x]/a@t̿캣̟XάguT~2V@:q7 )jQe.}HW*5Sl.UZD Sg/;(쩦ARRVAVlU +s=  ngϮ<}t21>D ȡR#9] T +Ku&,MX9 +#4Y{Y3L z5/jt +p-)7<̿>VS=b >@-e K" +u?Z3s?E㠊e%(ZVi-گ]olەn99TcZzv˥P7If=Љn-Ὢ%DFPTJwc<|hPsiɇ[QmM|+L`ԒGҽdY86*gy֯FZ$+Jm嫖RR|;; +thm"©ֿ}Lh5e<]S9~0+ K(~ vnFpJhtl&_ʮ]k *I"_k +_D.Oii? MDcou nrR$% qN) +@/@{j_*[gD/AԋF +,#|*9t"Sª-S,wQ=+Z?WHY % %ru֑}.,'a +i2#9'u 'D5y8 }~g=j (s +8 8 \CD |%Y +l& |o`t/8M%WBpjmF7USla|-Hkueydc?lQT4OKmU:9o;/nֵ=u!f0vn9%mS9V5ۻOA8Րcl +?,Ckp~M[GR`Nבg*l q|:F +S}f@v:=pA tq0A: +Ԅ݈A +_7zv +ug@+/U:D0Z +Ȋ]+vK9[j˳|A]Sh +^(kB}J> +)F ([7eCh];z'HD]8 !16q^s(]@( -/%k +JCtjבg*l ߑ)";vZo% 9M~=Ͽg3o;y%4ʢ>6kY| Gu&\x)OmX=gMj~w|w]4ʑ7uG\q^(|e ȿK_rW +4&<JS!iWl. %v<ɞ  +)yr!,wو܄a 3;72h_B,4ۺ$Ιhu?G0FHDӤ{[a qz\B<|ho*fqp,2|_5=rBy֯ƀi&NIPa՘ w}c}O,}ӟ\9 7ͻ8¤^rǯQ:?ḣ{tҾ7^r±w;qٌ 7W~7Ժ{/p~˷@*%f{zѻ`@ᄖ7<7GKH~ge'DRR?k03O8!&]<jKk#FN#5o;]$/h3Z *"by/PL2gO y9YLӢhS OA~r,Ŵjʪ%(bP +9\0# +9YHjۻ B8?|jdiW'tL9fR4] EST?3#P|qVdžEpi4us.Br^M}1Öp<Z+1: R¨| u@0%AG5~ Srq%g!GN;.FBJ\ $*_~2`(@O*)@b[' +*^zEBkH $В +BxV~X:$VBno  BNZ]BE"e!Y]L<:7!7~8'RMRے[٨^i ֺLXEC!"JH$;x1y'$EM)HPUK𠔡Zs/gmw8ǢSW4dp"n + G- c + P +.ihlN{!!-J?䐩uH_ !OPeUJtbU8*5?=p`; +3YK oM3&?J/,@l92ZעÂ`Bzh7ICTEf +@qBk6+[ Fl)zp^O׳1L%ߞP35pxT +e-o=O4R_hBg봾Hˀ!v:\4 {{y+m l6vX͂Uq\"%a㫣R +RCe,nvL'd LC莤-U2dHgߎ2,%Ԟ7 ѫvLЕSGz2gɣ|iXh` +/;G:5xΖ|ha7Λ8nhZU]&>|LJƎks5߶Y4˯}˪n'^7iXʝhGsz7AB-{Ki|>DN +–Ÿ8?h/KC;ۗh# 濙/D!z :EmQՐԷD>Hy̱CT3Y0 T΢SjfmK 0@ꦷYۛٻ, ))nEo}] ؉գ3lƪvf%L C9oUmw4V!%Eڳ۵ V\OHԌ,9NBbQ)Z$_.v`IZٽFFgkO,A 2Wp=)z &AOթiV/ rȥO֕&ʍ.Mbws06Wap+cgd{ R~^,ЅNHG'N[Dy(|Lz Ѳ:#O3іP(-G1f0b/PsmQ4OBA0:&GYBu׈k5]1ښ.V-llajVP td FW5n$A#d|{ Q~e i &2ivͯUh)DJƍs,y0?Mk~fTn"{0W?;"G-ZG< (qa)d07v{Szn:ʸJ[L`vDDқG]TID|Y) +O>+EDC"&ït0("DR +T*&(HVJ&%5G]+cR4 ++&bh*@e0` + +Ȱܷ^4-UV[1IUj.G@(:r 9ߦbDTw&:eN]GSҴ3Ff_7{lJ͂6 -ՔIvACl +6Nו-H)dL9 SCmԾ` _:>0kzaYM wbFeM'^LuuL&Bl١iO|_| DL8WǞݬ^^J駟ΔIHy|s/SթlM +{סFM2wzҙSOQ_w__>õKgܼvȞ.zwZjc[ +#)\ /# @v 0JO̙X+2e +Oy4jR)&!8MC^UŕוBQqA~xJ׋ib7܉%QCbnTt2b!4䑍qhNA61M4^Q .G vç~sh7<^X9.M臲I=wCzxickm2- IN afAZү&Tto{̊N+͎N+M'F0uQU +uή/'.P֌VsaJҝh(OY*>f< +GM{L\'tصhm.͍3<]?]c sN>+a]FfmԩU٩D"J?SOڟΜ駞z?y>Kl/ryr|5OWʗ'׫yrBPW*m[45x\u[LXmΈ:{@3Aa5J(ј|W5ROaZh!6+df#M5H + `)b\yK#B֖Io`:Ŵ`%3,qu +L.c#Z9e!љ{ZA `AV +:[YN躙ri"ۏ#)wIbxa0e0`YѿU +w򩁴1|ݑ෣򱁉S3i5kJYQtڼ!4j44h?*FdhʊOM,klmd,UѵpyB P~=%nI#^a22gU9W S^WNLXJMW6Y +J"(hUژ@U:C#(!D'e d,*8$$V;Q+M UlK[Qj +&@EAQaKJa槜63)eDbVuCc`\ˡ +^=d V\DUȖ&qmЊ՝8~dP01*A튟;[ذpRM:]~1{)RY 1Tʟ`?.QuIF_u/o +˗+Y^Wb=KۛjפvMvo<4~G,1stу{Ϝ郖d. + + kQ4kM +~fb^geC r&LgT$fiK?bí7=3@͏l jAs\t +-KHnL:JٚZH=&P)Ʒ/$;rQ0vU?ZSf!u谖v`#^2vbנlI2gɣi:C]]VY9s*༾'=nXra ?>wlkuۖM` _9!=}#龎-?%K_ٱu n[w/n]8qOx; F?Ѽuۯ>UfuwTaB2NDz`=> /6wHz O(yD¿! ?p!Z# 8ፈA%;Q7nCdg!o7+4u,*?10V\!Ǯre, +F+oH;V*XM( +RΙO($e6-k) ZJ)eoVo݆ᎦV_o^|@aQč]Vk)@vv-ҬH4HfXVEx6W +\J'*)AO&jj'4Ozrpb|fb +ėV.`hTZEz–QU>Qmpգ.oxlez:pu l}˕|gΔ陧ʝ=[7^/^Ͱ!?]aEc,|Q%t?L/'pNߺ$pu0#' UMt9y/$T, B!:Wpj$Q2\9Oo/? PKD|b425GDtB^uJ5,Yڹ\2|Wż ˵. +=v6] +ivTh>HcNX{Ut + +TPk7ٓB*[wHZef.74/h*'6̄gv_׼FX KNG"#km֞jIs9vU0YV}eR>"#nU P@c~78ecX-⡗ W1J!BsdCk"0Cz +KL %>hV* WZb|~B)[]z>NÑ^ d( oh +?e +/7)Qb :@WB0/ PX_!' + KC9@p3e2\ R~+( +Q -d謄X>9/? }+;J}E4"zSJ` 0U )*7&WD;2@VU^ +2:WlL2`@A3Ɣn% +2"qFi!(# g(QBD7FSm5`RX4}BZGK/"reR +[!1#]* v[9/`fls!|rT-zk%%Zw0{&nTgן:T e +~w:.)ѭ03U7Y];JAS-8J!֘ +P:&@^#ˁ!Sfǭ + eLP1-r4B#V8fDsztqZ>ݢ!tM1=/?bZ8xh`d#a tR_!2e&@AZD>LM[0ɷ͜;(/ {e/`rohZ]OAQ)@4R6DTKC@W`L ._#- K%GN1RQF{tU"- bp6 :l +_u;<d.b @ +hr"(ŮoؾCSWM5ĶF6 -6pLS8 +9@F djy8,}W`KuER XMGb^t}䓲pX6}w)T)y%'N&<* r:l +s@, +jĺz*&Gd +s W +%!otIF0%H ?vF5""kkTH5`K>c'4Ak'ߒ:7J=RԽG,}0gXVȅdm@7Co!zh 0'z +o)U2B~(MUxL/"БF_[%Ov +߽{wİa}z_K^x^xa`ϣ{"#CƏ_K^x^xa`ϣv%/ /0g0Qov'k / /y Ga^x^xa`ϣO7nܸz*^x^xa`ϣO_ג^x^3(?/ /G?/ /G?/ /'G?/ /'G?/ /'G!`0 '“ - ~֍+x4+S7nx;ȕ7\%oӫr}n964޺uM޾yx/֯֐[b~j^Rf;o[cpKBԏ7wƛ;&=jܺ#޽}K%nuh]v+Zl땻?;V~Wϴ/_~f<_>?Z~g%"޻G_OT=_~w{S +!&aʯ&]q¯~UD;%~bc0gɣ`03g? ?3 ?3 ?3 ?3 ?3 ?3 ?33 `mۿw g0 @?nɒÇ8{z31`0 ۈxbυ1?`0 =gO#!fb `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 `0 ¹>yGw&у +ՄKg`0I>4u#n6O?Hр_rCN;L}?sڵ^pr/`0Ǟf\7(-Jcs;`@N W\t30g0 `ID_| tOf`0 񸳾]_"Otҥ3)^ 'g?`0 c$]_$r_:~OpVRtG}Çv`0 "IW_7o:b==U>5[?;`0 E nqDdxYwd n޼xa `0' /~ݾu+wo_{ǁ_~ X|;׮\`0 `0 `0t.no@pw tw pD+<mt{F#C;tơF:%Fŀc+ ,nzQZXؼ6myE͚-h|~6o$IͮW?^̬[+Ww(5ET2Jn]]+W(%aZ|PXy,;@ٷm#UuBkM® {AuϠ[N.nq흣rD.> R/ @:w~C34 dR o:[[^1'9-D d=2(n٧]F ++/;\1eJ(Yd@|~ [Q@]}O~䫀^xP%P Cy@y@ +_52gE9nJɞoG ohAl/љ_ha$hB ++3lҥF,1xE .F]@2WI?-[{ƀJFSP39}P* F_u%9?dsa}u̎ޑ^^u ,ut/ߟ};{e'K|nm*xvos۳L>+YbզW6zijTӪTQ#ĥ5&q ]>Fpލ^J{zYS+{OtzT5Y|fTQ$>}|Vt8G5F͹(`*Ϭ\iG4 +UYRlJ1ܣ"ܜ8Mv*?BeK*>DE">_^@Cɑ䷋P9J'LۘI`>NB9ʘ-K[C祇ie*t1ɀs(C|kp<#;ZnPp0ȵx޾g񆁯xK| }ء/=_:-Gw^?|@XyZ?sf̙d3x] +R$&MG|ԭs݇|oh_I;/id)9ҙOs'O;~Zidj>l};{zHⷾhrfF3yE֬Q8*+U +3_?}3/y^>6xe{sgޣfo?3bcoL?e,_fiQcJQ*U25$MiTsxI{Ys{ $/ՊB{+:ݘ >xV\9_ +!@*fV3trsRqSeKvև]L +5C*zVEnnV} yW 8Z㗽:Nh۫"B[naqռzxy@悚5ՠꜪgW3tvwwu\1bY1P +S +WV*Q*6Ee"L(4 g ˬ-rK-ՖWWy.鏚}R^w~o)@FmBf ۃ) +yGC퀟Cڸ݇ +e\=yBc1ćgMoET( ; Gk °hl V, +Os"O*տZ{~Ȁ3H0аGsnf!h$Ê0Ia2T|u~ɖW6YVYh1,IHï7?Eϝ|,'},_T"hX+&kȔ&>G?ڇ>k|i?!ڤ>tt|]lЕicՄ-tpČ-Yġ0BVYFHg7ZpQW)X\VJ%kԉ+VY\]ls9RYw'uUG lF}=Lr3y~Gk4d\CPqzܠ{roםk}So&OhނC>C_/:8[f??GSן@@%OP!Ҫy8.ELThB>MD/)eO}[.<)ħݳws8ݓۇ +tazJr--R#4UkbusϿJ$iT Z`0 ~ +UOܤ!2$ypհQ> + ^ +aDP!1^.l6@[1R (APQ!dryLѩPt(JAFsP9qr@?74!o #aa~c~c! +!T6-:=Nϣw fR,V! ,E,N1Sqe<-Z T +ǫJB4) +y\V$ +1$쐈ET.{+A.&?_ yW)ÁC5)Q6A :t &|pr; +eػT,!&H,$QpuQ.$ +Y݉ay"94[* 34>{+EFh`e)+ČC^ +{=As8<#ֲ#cnX +LnnP6R7RA)I!0@HPS!TjJ(X M MBSd#HaS43J2t.3N8|v +Wӈ`A +J + VS@)!BiQ/&| IZ I#v# +1&w]`Xo,w-sjlKkkB(qܠ7ARJ<$Q No ʐD9 C$!#^Hh 2Z<ʈC 3%JRz'K1,KX2N<Ɖs +endobj +169 0 obj << +/Type /XObject +/Subtype /Image +/Width 1024 +/Height 768 +/BitsPerComponent 8 +/ColorSpace /DeviceRGB +/Length 386113 +/Filter /FlateDecode +>> +stream +xXGO_1nbEJQDRA,tTzQHːrpd=gv7SDv0r1 ްXΆ` Q`0 pT#&Gtxsp$X6E?Y\ + +>ߞdIB9R`0 q +nɫ ۠.З&]::;%-{3g2t~m&5JŶ+? +uz2ڒUU* VWIl*^sOL~' WV/^/w$~ +2h(u `0?0× wt<;wYu]8pOKS̳mr2*n^dOٕZ`/uLi=/ZsVF +@_Y 2BYGyed?c`&ձHux7a3rݡOA`z&?#9,\ZF֪+?SqpqEYY`?)rWشiN() | ?EiӘEIhmվ +Ik?״l-5rPi/aM1R%7ru6 .BTHR0ogmaky5_X5 `0銶::^beoSoP-SXtَ_diʶ-F>z&0, Ah0Rl0=]/G? ۪o +A|^ +^f\( LпX;| &FtAbPض^- +vC{\|w!{9$ʪ',7r6qn1k +[,LwB¼<[޺JvΡ`0[i2o5agզTo:G[{BSei i?H~-mIj& 9L&:=b؏d(UbDV^JwP} ~LVQ9Ȼ')aѓ K>#3 p56ڽD<}& ָւ +3﬋F-N̦D&lMc!\#j,\Qb$(#t1;br5Q2lPvSW3W5J3sº 5Cא{XCkyOBJ?`0 fŒ!nN 9j8p[<} +m2Ŏ_NҾ}'E'MQGw?ɰ(H ^8Q)NQ<@.^yl+bw?^|;{ށ,B9I`I䏾#يnI &yŔ- (dD~Ai"֟tic "s63'v6w1OH!ix@²tP=L!'A1,_yBlC[вDlKCyH5(!iU+E3Eєn \,K ]eQhf -/Y'~ Q'(d XVx{y*j_LyR6ue6`06ูFC؈.b1!.ݯG߭Mv}}\qg;}L!̈́L 1L&*^YVPY3S~W-0Z`v9;//S6V#O}(.*Q4&uV+:҆n1]ʹ>r=ߛg<Ô݅taYp<;|O:{ȷsEbV)˘3Q֙DӁQO8΍>tKZYOsG˭];[SPǩ"& ia涗,9R̋,[?wݾhz0zyۑv3v4dkr`0 3hB t]v-:&}_T2aTTϔ)F=r`01gBQ0L& +_oM[:4 +@6:s]GV?f^vإEZeUG)Nn1icٲᲆ1 7牷=۱][>ߪ1c4sE$1^ّT}eː8tC30!#9Ҳi<|i ,,Ni@36|FBz=4P%P:Ik|r*4)1E>Cg)7fF)Я]KD"k;04}Z>B,B};k s"#e2tQ0+ysCM!vwi 3$`x7?j'ޱTg:֪֪WJ[F[ry"!KZ^]ñdv,U`0 -Dzol ?`0-Hݣ V`O =2ڇxӌ]F$M垉`0  @tSmGe +7b-;e\9ٍ`0  k';}S-\ث?`08?xq~?cpfq_?oWi,  `0^u>ΜWÍhcY=?6 `0\_;z'ЫGy#]';rv3̱HeYF6/u٣jߑᆯ`0 ՃW7 `0= ӎ`0 `0 `0 `0 `0 Sm`0 `%=km <__5Sb0σ`0|`0`0H`?`- #ߒ=憚n'b0޿:`ad"JkRv R#=Os xI(N*a!!0.>'IV`h<vk460cӅƾ?]i{"X6Frqi.OTek_>xN9gl"9>*5+)ُpm㟇Z~X<ށq3q 6//IfztZrM?l_snVZejR8 }h0#QSSC?olC}}4ta +E2rLI^&xIUVFL{i E'J{8>N{*)zɝV % Ň&(&-oj5e3*<g [!^A gd”Jpс@8Xr ʀĂg1Q|i,,"o6lh$ʰ?,ا0s{͎Έ)+IA]Qm_[s 9'i(Y\& +И_03B;1k] +vv9Α,96EY lI.. + ҈4KO&YUggI$Vŧ<~]Amin-h.lKTְz*kPJL +VV:;u^qVy +LLN"\3s󩩙Yy59ufv&fVq_߬?d +RMk+cȓ(P~eL~ T)%a;k >B3Q`,Q(#?13 b늀zj4@`[s :HxPMIFOC"t. 4-a#{_2?.)̫1Hʻ 8˅&]/{hfC aOf- +TB|¡U;v աO"U2ݼ Ny^41إx;o~ְ +fz_szo/K>wprmSqѼ#qnj%S^^EFNm\byD\G#Q6?LhQ'`¶_y9&R%*^sѰÝg{6m_1gN#InܫsD'E-~pRs?IoּzN5lpfτUcEq=V?Oh7{qD + +**r+9<n0s'NʘOKNC?]BË]#u +kϧӛۚi=/;{zi}/{ zj*QJ6oA__v╸ם JXd. ~ u ƤtHͭV[ʜX!uZ}(hn_jtSO\3dS@-?z1:\՞}{P]ҧt.6ﻻ0.dDYV(GGSH𘡉W} wyKˏ2GYyUv;P?NtқF1΢e{oRAȳ3b +d 1 %E=5=UΙ(` `VF ,Q g"u f9 [rE6+%n.׼o9{j u5!$ Pi{ە@# "xe!G( +?l +]1N_u^V5,/Ǯts馵i-ckniW״e&TH|aۮJka(^g=/*)yv#$P.IZ; B8n8HH77XYתk +N +Q[Կ fp( R_(DHA Y֝F1@ h@91h7U, +xüOWQ\0V3)`t9ؙ!@$Ĉ#o'v +^M +LwLC[ZZ^SF QuF.Y+ m7~_ԵBҊ:vGip3_QY;2!8yXN٥[M +ө.fT_A/,ksvdɣ#WcN$(Wb|nٲ)ǭ4eʱwh.Rr+ &L* 4ָeh.X>IVIhdmmL.ArQaygϫ_2u=(lԹG +bfkme̱CA! 0|jC^8>1l[?A#ddu1H@+C.0?23*B->h> +@3F]?`' =5̟&gֆU3MM]55'ITN7 +K_ ~~7`"mB7'lߟnQ7yOӉ+83_KZ>3͈B4Tl]/(hUs?{dž~Es~I(I>GQ<z .s!ՂRhVqɲIys?|^T-)c? I&G#s d +[D[;~W[ mӮIמ0 iٹ.-#a x7 u N?6lnv6m i߼%a)nqa]䨻uQo pAٽǕn'f"_<ͯjɩs r@lݲb>7WQ8I,7 G|Fgpfiq6j /~[s {j v1}4ԁ2΄cvRSU@a5@A -f}f,- s83sX24]PY +sO8~Zaw(9Ōe}+ˈ] anYU5$eU7 +s`j ?eK(ߌ({VuO)B#⨜ؼ 7['n"p8Og&of +,v&-O\ + n&"a r-Y& +lwIFITzCHSz1dlS, #j ӵlٺpjrf>n`-S.SpE3:Qp骅>9<ge*X\$2R-(>< hbDs5Uh:[@5F^ +PRX y xEIkCzO^Xʂv"g"~N+ +n.mPK|h=v uXvAW(,e +O?\̷r띇b͂*܋~*eܻ TϸRO/8qT\!R,HDCٹyj1 a@ +'/[m|sZfkggǮmb^ZZ:~QDoQ۽8ѣ(9*q*g=fFf@Pͽ&[D [DB/6zٹy7" Qfם/V 66d}/j"eN}mi}u't7Kn%+ǿA@`(z:GeO0?  Ƙ>J | yY7 +}$3h*Υ;jIK4P~)̤(#˱Ԫܧ;l?+Y7g9먜Sj|톘ꃆ;N}ú^N;wVEXPKDT#:QLc2ds7s'>j > +C'ivYz(On4'l5eu2Sܞ/6;ֵqq7f0!Giϥko+xSZU^L(_rcǑ(4gwU]OkG7ii9e45gUmR+2jDI{YWWҘ^PZДY1(,^7,)d]^^W|S}8WNGk9Zd!b{B_w=U dPV[dqdǩYٞQ97"s\쁶@?=DqO'̻ȅo="1p!?l(G17xuUKcmn[ٍ>zFOwOcKVlۢz| +GM +}|3O!p9D(?ge%<=/%Jd^.s^ddj>1"~\FѕS3:}Vq>Is]lY? +ɋwN" +Ibo<`hРCU.LI3m|/r@y`ѕŀY`齂Kws.ejy%|?{qN9zO>;ĽĽȶ\Ul۲ @IMTBBHt{Rv3l rdky{[LB +dEJ'?-2B)|=:8hj\nu'ghrG6JwVWWs]ʓbttI~)~)MJ$4C LR‹y#//$TqD%wbWbBxRhoݕJm{&w*F~8v+wӑwrOzm5:rƷV!/ޝSXB?f-|/?Fwm^sO^xzh&V肜2ϔa/7 WUd>Ӵ E ?;П͗ٛho'yreZ߾Lq*Gq ap3_w ؉S|n^[_X7ykz^r`ג/}}֧ pt[Ӕ?tNXvf̟AW?{aFכn +;-=G G'v63˛|^~#lKpM!+sdž;>Rwl7ͼ2=eR!-'|WuшUOwbZƷ5M*׾ϟojn3R'(nNV*Xʀv=oOnq՟͊^togc7Q'+z O &  WHe5RYT^\sGh<ue饩ie!V +RիWӗ_ q&?u#]ш;6Vdž,1EU='VZ<.a c! ciU//ƺwV)阼ئ7,]2OL+ Wm:T\j_}mF7]zhٻ˹=xd>sI&(?x y@xroM(s~K>|̰[Dz.zYon*Zzȣг{גM V,.&Ve\4%IVl +LuY6!yvaŎ" 9wkMnRZ\TX@o%|l{|OJZ')uIU>Ie +35"*61 Ov[]򿚘YW[(j{Ҷ^nOݹ +F㴊,}V +ɧ˭zXa;=[/ mbbO UG[=_z|oT]eNVth׸E:q]ε +(#9s u$W'%fنۇ_Apn$&+F?Ie>Ez⨘WXW#3""?pjxrJVutJK!)) ʪ|zɥ>R$]wo4/Q +3t#yÃ/='d^hgW>'V|VaJ>mwl~~ɚݿ/Iϻc}/}/pKsI~d_W[G&&rdY]BBmɈyȟ}wթuBĩ"f30;eN:r2nﯲ\,6)z!4^^tXl 7S*3xC&/x2FxW2ȳ' 68OϘ +G5wWVVsSxMVo>yӑ + Vյ`㹜v-<ܑՕtNrǯ=kw.h~&;}80O7Ap9O95os}?99rr׿-池zgըS٨Miq___: MP̶wg>{knKߋժךuɽ)]Kuoy]G)^r#UýzKO, Mmӯ}^rtCT6M럭=.vՙVVvvg+2謌kijE lP1.G+恉gR-@AiڇoFLbJfZ^I#8l/;lZ]\qYRJ^YekU]OEuwjv)k :?^n[cKWy/ y#1NoIcqAoKgW7!X_kRYش>ﭳ8{~c%# vHvhA 맲U#qbhRiT +2z!se{e 렡m7ش3hmy]mZpV~Uǻ_?5'o⽏ ^Zy'hK~G=ңkSC['iàWO*Mإ+clw}檳˔?c!WpN1ޙ2l, +6t̄ 4Y|ڼܷΧ=z[/<]?޵:s{ʶ:vA\]zxlF6bYZ\w/<:.%_<#'6J߸qs-ʝutt6]K + g Y9G,[ tOx%~.^쇀 +?!3\ϲα %lCX]ɰ +h+S擥> + f|~þ~~_{dCڻqө~y-W}Mzm-͋o<}͇jsO Ubv2p%"_9±b<ߏm'XZ i2tpA=~ۆ||! \\~{U̟,C 1rtD"6~yϡXcnp!^v?>79ԽE- -GbמǦ:=ef=3: +8s9EH!l|ߋN8zmG?;vZTRUu]OSK?+Q׬wtO)KjKsandW]wr; cO/j<_mm_'VyI2˶67i[rב)Ӗ]/:;24  g7e@HaW?=rUo]\ɵ'l`Km 6X9}ljAz{QehS7y@>;-B)T-u%ahu=f GABd7pY駟ȋVyظ>}tkb{! +pC^F_ E'޿E'ٹml뢴Ĵ]/QWPvA>,;QnRmjA@K[,2b -l'oqQzY^Q`Kps$aKkrxq"c`/:-Cxy:\`{ yCP}M?k!ueg #Rj^|%'_]zRP>>Hyu}OIEgFnSHTKO|zbFVA)?(2 _>:y c M/z~ 5_e2\N+-~Vn2\3F:%l9[.ʵ7k_?gpJ;,]}w$?O+O"X=}~lYk6IcNB2Ǥ1G@L) OqC)%A8Eאӎ 'kANlcpmL*<Rdj\'p4/V?]"8 ypᲤ@ +'D$K,}h@Br p8 ^SC\UE0|</ 0~B>ӝ j&2եxmSH)^i pśbN}'\pH˅N3X%BBrY! PH ' k\pQ!;,F ++%* +)|08 )x, {CsKG>< sW8 S-|'N/ߥJw{8V]W!DݢͯV`.`IC@]!{`Q0Ɓ !1_ V@3Xm)3FFUB +1oEV*GhJS) NְbAfDi +85J jxV'.fbMs1~cp>jhVLt)w>|~ h-Ƣ(&cg & +|PDma +(C +gt(Mm@;. +1Y~qP-nyv(ϣj*FSMZD N.Y +gZmYxCabF+ cQf|̚a ƒ0CKA??i/9?K=ib Qdxaf)4VKÎ`` 9@Z5A)J;OTNR +Ihi` -(;Q~^Ttc#\dΤs!1o*te[4쁡sO) <C2G{I|@gQZ_ +/ _ZɿZ ?ȑgԴ0(e ͌~0K<(^|RYa`df?}v'2X(CP^fB)GZc?s[PpRm!R':su—Ccx"'L + pR pY +`2Ġ.`eTg)7|[/ p/|/PM=*??77:Y猅2|}MSc(HpWR#jExb`$"[낍'f +9Iʛ@.T2Dw +ViUh3h +e3Xɓ +v?jR' 60[3 +dR|@vd`,f `q&|x e?9+XY S@ggT_S3?8T3T\Ǿ#ёR4ʶsQ#d(8vL.nzp-&߃w4&fb9 LXdzoqr6cp .l9V1MUC +vJ~8@]\{4ڛMgC9-?7|nAOmwkTє:F8 +?ي5>ҝEL YSܦ!,9IFN&0EtbF>rF`:P ^^NcqXrXf8V К|) +~/MOJ u'j_!U q P8tbi^XȀ` ʿM2,:p]R. +?s#tQ'a=F! +X&TBࢿ@GyeiU +@-Ժ`!JӾ6s˩X,(.4.@Ky?15߄(snFXϙ +ӁTMJ(awt.V7+;01ɁK1I< +nBD'W> 69i#\4E1( +(@ wbU!VYKkU PՔ2Ymţh/|bΉ +:i&Ձh`7M.KѨIIs<1ϫy='Y5aKS-&@ _'}L>mI+{nԨgͨG[%gy%j&(9O̜?zp@j0UVDts]XZD7WȱLzmTc}-U)59pi?<&Wt %@M\TĠ?xrP) +?;ɴ%x_0ZK +ঘA?tp.ՂMZ5f0Z/*Rz89/Yj +c7 +#+ԈY5DcSx/gT*ꂎCpR^HAs\LIjx!?dEwЗQ9M91^<_MA& ́e0cW7> BXF m3`(^?y6%aBoWPDS bfG=NA2yO-࢚137*M;ЇϗRQ;p%wx@=q:0?,Oå%hAmr4`- z63"P6FUl"VjJ tn={0S\7kr6SM,Q9@e8J82`I g +=NkPpd IǠj*jke.)~Yz O*i$%:ew 7$({SۉܲFjF>M26EUG%˩#:0 bi -]ZXh*$P +%F@h +Z~.J|thcy4j(@ ;UEjl>j|QkpZ+Z1W$$_i&h6Aj̏Ev +vr9?X=USXŝUIiQ߇'}jЎ㬀!mA)k?=Stw^,?nRU&?7 +0YcO6f7ЮDڔ"i9`UYL! +} :QIͬS7*~-ְ`FUe@#s"%0#aczRl$>i>o5hQ1*^ls5Wn_v3Ҍ +FXӆVm>ՋI~5ɿPj5̟SQP7`pfB"A+E19“|IQ z1Ћp+%ÜpQ/e&îXl@g/-f)0$9բP!qSYyRYbjq< +5XuX*&kfQ_ͥo- ׺~z~n9|.mǕeI&J%4 PZT-n#1 k@k9_ʡ}gKED 9V)| +h@?ZtzK~2 (Ť騹H#1@8k.AN5נD2U3fXIK9EP5f +u8=3t_Mu4u;ZI~*W#>/L3kVȨE8?E5jE~q$n2M5?9́h.W~9qW:FB3jĀ#֊w2,'2HݿKY=qߋI4j`^N)xәh~(6Kkę"q2Khq"uskJ,UAIf+3?dyc,TgGVI2EIXu RN RM}M`zJL1a rgO(O.5P VY(_/Ι]$r +~QO"7qU.Rp\ħ|珍Fґƪ~AŝvB;/?{—[Q_?u V`> 񏁪G+ UK}·j(c-Ԫ9?)XFzM03o@Oz9(ak? @LX.TƎ`F$p-TSr:YFhY" +gcSTX.^[fh +nqvY*7W03G3Ju26񔡙k%RI1-Jf߹hwy,S.jc ۟Ӧə`=%IĚY.XpīB83_} 7雌;DS6s!ŹjŪ_}U)!@@wb00w 3ˢ |?::VZZ ܐ%,ët;y+튟:_W/G3N |1&lNs{ gq`?~*`}>* MDD(.PVYcZ|Xo[?{$י] "6٘ƎFH3Ҳ{4RFnM6lm + +U(-po[{{n"H|{B )= +e"bUjy@?dßfN: +~ ksrq5_ehBJܞ +xrrrg +1h +{ePƛ.7-/, Xٓo h'Y(ha|W,L3kȷU~Q_ lo lB )@.QnQ#Өbq`owOO[wWkgGSS}EյDQn7>?|_ۿ{~d7Y}ܿ}rCN|&)?|/7vY*-<ϰ! KxAWRQ97NYDB?ΖkILM@ev`R`_@rr88Jkx!?;*p{jSMd\B2 +*`"GjX?JK#ȯD +%4u*7j-k X2L 0 +H[/Wj5Q +P, &)|z0r7.6`64AlvKyh_r5"ؤBomhn4P@#[:do4WS* zTp\V?x!╞SǴ\@S䓮՜Dv04{48L@? nat;ۑJ<9>R#PuSsl?|%N p_'W'/w(ڤ4fg t/~4'P貰,/%5"ʕ2#ZԤbN5z;ΌfJB$ee! V &䌯 9k`(WPfb@hEsލMf? nP1oAkv: VgQ. =7Ս1% `;H>A +pY|eڿ"ܺcsAu+`FǑ;0*y`{P)c + dߌK}{χ84"W`@ +݄=g为qe]55!"0};d_7P HAJ +6k`18Y\CZ%{jdw,*X"[hx9r.A1zn_?$Oo[D WO۰ &ִeYj+>]8?KOAN<)#H}DUP s)W!0RV;1 +tݯ` F h&z!J˄zL65|(oŊ_4!uU)(t +G~%@` ZJ@``j≕d]%۰}Qx;D?&&o7^wVKB%8keGhJY!juUyy|p=@ +9L"P!pg0L9U*[|Bq_Qx[[VdA|lݲ MQ'Ш](gt4khofPݠ + +$ǡziB=s/!F}`[?i߯ &.0[8_3^;ݫf4Z;iwH/o +}F كm>y{%^z=w(g﹂:>:<Ϣ` !sp: +P߹$Tb0H|@yz$ z_h"@q}f`ߧ˔p8,l[)_Kki> >lPi5eǖ{J+WGHȆ` +& +#h^Bu('τZk0 l57yJGdߋh8-xvn/ +Ȃr% U@j+4,"/[EE EF +R`+ _"bRXqFGWΘ NO%Y3Y1zz0oҶS7<}[sZ7Ƴx,1lh'=c|6_W3QV^nc;S~vzF@e} oIe##$T>_G +3 +pRBqu+7vI > +3@C 0e>HkvKw +P( 肇=TP`k1huR )t@'O bպ@*b-v7N94,l~RNh`L+ +$GqV}6$/Zʁ׽`$mK Vɮ܌w d=j2;ƈdcBCKM+ͼ7.eUa bdwz!yrwDޗ3S>v]V A ,۱`C T +2]U W f~'E \Zc."vI.% +MOd*k{bS8VSkǵjwV@"Q}xm$n +o9~p͍-@h_aމݏQN 鲻r +3PW3 +Iȓ߇62!ꩣufd֩:e!˸Rm|=b|E ='S'@˗s)<wy +RӥTP! +`埩w/~"T/(rij$@L< +qc6S6&<<p?,@3 +up1*۵? + +=vO +2ib'nE iځI?gVzXrIMoy d=#|`-H% N3eͮAkkPi7U藅s>uAn9%UcH?UA;IPBE UtbIEۘ`&,4`gٯq:=[ZVB(Bk`,™P{]goF@{#dT +\/ +J]|G?UeJ<uDe :FF@J?9ˤQ İW(ќL[=%rU{’ZI`ۆ" +T셅&^  vG +0PT\@;{AYibp{V| LٺuDyg"P1 g0 +'~ +&'/]6E:5:D;ʅ.1Wd)A:K ~Oퟎ +Lgf*f9d3WRÀӬp ܌] rЫ8?ZI?\:k@j}4zh"P%UFZ +Oh@1A#hDmY ä +@W=yO_ B]t-~PJvk9d̂?LbOk]R U@?Q\/P RYLcMŸ@\} d O"ҬQ2Z?: +{F[Em1wlXےFDsnJ;00/Zw#~ TUphs8zK]g'3)/# +\e4^dXiT{*]@ (@- +̀M5']*)h?f B\#8"SiOwfp5w {a +PKfǺH,?S=x6M/"((\"6>Z*HXײovo"5_D]W_* @ +z}b +mI&Г +L%uSsYe-EǾfKY(t] +5¿Ƭm85fGi]&ܥo +<"#8j +p% 2S68,R4r~'L8{ &֐OԪy2ʗ+x +-+)|J32\Bnht:Z~A;0'1H +lz޽j6u +m4w;0P}8[iBȨ#e* 0x\C3/u]Lw[zRT n !-VzWҿű~T!Ft^"8 +oTם= +`Sq~Vy&}B{ [OP/m:Mt@@mg[~R')Pǻl`I:߬Օ +7loREzWB/joW'L +QJFt_c_YۄNl-Y*s 9\/ ܫ.D@ G-CsP\Nѥ*+| + @׃WoW;uϛ|-O^?~?h'G\?Un]VjYg|/hV?gajR O!uT>t?IcI?A0|$~px Mu=&77"ݸVV֗}Epi% +qY.VS؈ҫLۏՉx_]ОQ]}1"-.iUXi .7æDZ⮐F Rwc +5I'[`|)`>* ؁n CH#-8k=c~#ؤP/W7|B{@=n[yKFVE6T-<܃GI +VqhQX=s[VQm J`*źzu + +:2c7U6 `?gVFzM=]?Fu9-[༴/H;Wi%R<[=Pb.#Q݊j +">8a +>~u}' Q(q~8n_]@_"P 悅%@@hGL}9sN3H*LʈS ;=' \C-\ _W3S PrO)@TdP~a% 0&f00?l"{0?vD؄⠗{V=.h.ɺ*?&xbד$p` dtynO_RI * + =Koz?$/ZS=?rW +5_{>?k6~s Qxw. g +43 +XM:t;5Х$`!K z$^ +Z~u%7N{C.ǐ7;Pt%' +נ)c[3#}'}5G7n\:\ W=-I`8ibU֠7jesX澢ta0FU~tP utܱsǥ8+A,6/0%\ l(al- 4#rO.> +]X6:.RVc7 ׋J٭Ӯ)T""Ћ`uߕws)H_%4^)o{^JRZ\g$O^?o]hKQV1Xl4hUSzAXe i׺Jj1{ˌ}uG +dam~vg80q6;G|A0iyk&Zʹ!Md=jo/g\VDyPo)&C=FU[6h2ÂOA)UYA +pLp*U +CLX4QXR;F@F +/Гn6>Y|2d:#'nc?4'c8 GHft HC8BqV(=eךC_Yj%O?I+(~훁x~oha~t=+?>Y܂[ߖI-i㲁 %rzbhN|ꆌq1'ݡ@ <?Wva +_nb;@ 0|837?}Ĥ :Cn~fH; +Б-Ņ5 +iR i@7?0?Î + Up; sD8IF@~?եso' +' CP3|!rLf^ {[ZC L_Z.!rc 3ʪ<2$tSY +۱geS}EvH  a e7=WzPwK{-V'/I\gO kY"XJªj=?"+q3O sf4fc8pk|'V6Wz)7T?Y)/VXg yH<>nqT\ +> +B* "<?xF)|wz +`Tjbon?1俉T +: +h +^)~V?{=_+{$O^\qяgn~e|?WӋܞ?}v?$S𿝤+_uR?DH~Ny'\=@l;E}r|nvh,j+/ 'A? 2sdžSϒd\C31<`!ΰۼZ tQJxWCD +`֏vU-@@$ p-|a^\ÈS \{0P]C9D)?M'(L1>ލ2pPA& +ހCT +0-@fSMlJ" 1Ӫ_Eh@>u-͇)191b8j~6v&&n +L!'G]4gikvjħKDy&+R0j8H:|FB(#/)S!-v?w @[(`TBֻnL;n +,Bx=-Kr##964 ׍p#mZ'=$O^h<_L58+"I%]/qd7We9U)"ɎFm=еMv{3V*U`5hOΊ?UQyEk6 lBѮhXِQ.$[㭽Һ`,;}im`d q +X醂T#_(|]˴2򼞓.]Mx +AeЃ$_M,#οK{$O^p'שT<;/'S^>q^^|s?{nW%gOг0_'&vw5A!ot5.\4~lumQ g8l0^u+>,=aÎ.K SdBQ#B)Q?(!UM#YZv, ](:Mթs +.𘑿$Y.5⣋"},@(5J-}:G?UwGU@JuG 4~ J3˰CK@`tK[?O`j jԝk P+]9</ OCȗ2 o)QI0 +> +(%@Hw5 O(@d +p +(JLʣ(BCn/,t?ǖJU$rx_}GmӷƷ$CU#!NR/1WK%E +ћ#j1 +#n6z7y'D8>`}mG>w=)ҘMUaQ)VAhB@9BdeRG<GX- Qn"\40 PQ0T$O^d|O\[.>:՗,~qg ;˟\5?zk?o|뉻?vAݿe qw?EVXY/T +(.~g#,슀$S^e4uvVn(!9*Nh",#FC@)9*cՙq \ԭa3a +_y5` +($CE@”~OO ,z + p=`g@yFfj[W##:T;S]}oپ L |OPiJʔ_GՈ2v +? ?.i–'15+vj-B1(?3\j"Ϡ5f9 +SgF k3_Z4e6F.7.25ʍN9VWɓXM YӄMŞ e +)nOHQeI9$O^dWezo> שּׁ֏}SwLB37JF(;kEܕO+VZ +ϙ40{)ր!N J-+%viy3FHF86⟍)&;%@˴@]7]7-|Sₛ:D j= k(!T9&J6@8Ȗr@n}t7$Nb"g&Rlv *#.g .geնR{f+F}m4( Y! N8dxSgB>*57Ȟ\_XΪpzd٫ _ =+3IuلNF83<9+㷑 +h)Pxf2I9huPSG +פ( ?R!8?@yGݙlnޛ\[ͧfh0ߝ.Yh,_ + okk=." +|̕m)C=gs_D$Qi5.@aK +Ua +![iF^M5r0i쁌\y"Y̞/0Au,`7$:I@nmc#RFx +X@F/WphF@mۄX{ b=1@J^KkC;Ϭ.g 0?j~2H)V w7ۉԧ1Y凜nEɕ' +BԐ[25hQ9Wsx#@vNýr@%(+p؜>)REsR''dM0bQ!N +* xM.ʛk;$[X!RJ7TA2 +QŊi>4< A+*kSn +~#\ؼCC(@9ȖD`/e_^_?S$LL?Esﲋn NͧpSרd%e0TFSqBg ՀrWyP"(?KXwZ% b|J{39@gsSx_F?KUj/:2M~4PETo cn#p .Jhn_s9#@ o6A{\Q C+R֒We6EAVlq]+A<]S]}d1XC5Bk`thP+u]B(N&nh u-h 0oAyb3!H=GB F?,YD΁)m#qSd0\<~x`VA=ϜYcNcseeCMb2 Y"pJ}%`)YZ*jjU|0-pZ[ +Gg"' v9t'4[khF +4}h?Z,% +(.b!+ +2I ̾@Aǘx$G$bT~9LyK:/3~&34 D^$݋` +# T7%8 [vi|[WU{OɆ2~Y^eJ߳nY{Ot"pTpL@5JdUS p]^@ǥHY^Mi}Ju/C07~5 ?!1z|¥vT\&IpFfjTO3RNϳzZvh7xQ9ړK]8APcvs#~1=µQlvy|ò_q +1˗-=Z1OU+4OlC|\gB[G +`a`> +%6Cg-׼+`7'*G +Fr0N'/38h +<:i(k n"0+LhOw5) + +m ++l!+z +37]v1 + RCDUs]ya~ aącaJ䉈z(XgH=!m-V( +7 O +󺛿\wu770.g.@K*@0,q +{G<<]$ +C h+.rZEC0 O.^Z@hC I<6~rnT ś&ޔs̢Z; #h 0p +EeP@)0yPOF +D%6TZ"(i9L,B +Q9&hW1,ӥw +?7W9 +HEu +R +B@ +p㴚dBM0/QqTP[7A^!r>#ᴜQ }ÍFFfV݁ʑ=L^ƫ] We)#mRGsW<τ'@\E!or_vϞ?gT|O˝:[?ƶaÇ~{?PPzw.ԭ&/ +*!&83YC`5)/?rhq(w}NS-LY6r{pۏ]AC?ArS[yUYѭԻy8? +wqD}݂"iEp WWzP}jwp +p-R-l [])ޕYP4xv"#` +ԌMPE!|@ٜCGM]2U{R'NRqz E>>'p7 lE?@+_"oi?z)Ѱ{\Q +Z]6|Bu̹Noi^F֭465݅>:ڏ-PSo<7ӅfT+mi-0g9\_0"a462TJ +y:wՔgP?.4a +P!G%lSUXN7ǶJeERzEB +Ojq_P@'ۿ"5rtB)Q.}7TJa-2/_r_b Q҃VA$_+/;QG fJr <h>r}Dki\TU@h-`U2op +VePh,Z8:2^4^qR5] +7)r*$4 +l'%@5 y)?K + +\J K??W*l4v\]6RtB!®Pn\P +iRJ +0gn tO+pgIfzɱ4~B3`,PC}Ey2 AY^^%W)*9͓ +rf=[$_=D4Hъ/q2#B 4@ᕏ2~}Teh)BA+:e% +7x]f# ;83P eHU;6ńA$&0)t% ;J2P5 ٯʬdT~)nFvc0#!VGaGr+{t$/zh +AYQV<(R!wei_$O?LބItDc !ڿ67#]8W:ߒM?)˗.5)}JH`9ZN&h!r +$OܞD(CȽÌ@*/w5mlj1SS~'f[nŭjP1ڲb׬}.Xĥi$_‹G`PhZq6T- lĊiw\ ,IY JBx>w 8pKŰ`W?&6C`R0U1%@(8t͍j Nu.5kh]gU"ByW3/Kp&-L,Co@h@1 -<c&k4/LKS%s)$UERhQ"X%/k.O: Y Wx֍$AXa7 +"5 |u*~TPT3n:/G<l;:ڤOs'b175)${O=UDpMpمS[{mЮWIثba"σۿs%yi)h6%-Rd=_,O`Y/w3o[J"[\]m;[<:>Shw.u"Xt(h YXսrDQؙm4# {sù1?0l(B|aZ$V_b( +ȯ# +"Ocg + +d@\AP?<;OIp)%6bmk>7/ _SNK&0cR/ Mqy(KGf2$n^(ꕜd2e!w6c[ B]o *t +$GKwBcr ^LT޻Mj$,=FZ*W(riHD +笅;Fy[y%3 +ZNAk +c??Hat Xs +<%sc{]'Oow[+s%:ɫvr +. p*侓r^`QX#:N}kv紌y9y#Uo6e 0+16F +#GFw5# +R0=ďzno6#??0?G w9> (,;BK\`*Ղ/l]!hi7ײkwb<#,ӶgE$"\zҭUpEJU@|EPGP +F +S7ʛjQ'?W*{ܷn8L=[$/"J +&]>'Y2 .>LfTl>M졧 ;WM9CS0L@,^6 fJaAZjDeUk)*) +=,Ԡ@`B2cY/w=o-lz}+Sm~t4v ?J_%f90xaROT +W*|HD +? d~ut}NВ2(}7N| *@4S8hC,F +F'gdwō[S/sF;Od,G%5U8X'{Z`IYҶ5ik{3I +X;At93.-R[kԉ2Wn~z|ip $ +b`#儷D62`z-#Icgfsl +t)58(V4 u:V\ǩI(~;GAhna,3@Jyk d{= xV7,/쪌 + 4=Ll"7W#v2,? 9|lSnzf`bZG6S9Wf)Yo=N +*Sj9NGq W: #$0WA ocO?Vꗏy) B~LD¬-S G_eOP M.n,̧~.3t0s\;8083:/_]l}fl,OxB`WM * +H9m} \첱}Q?0w軆moDE? +wZNA57d2% ts , +h#W)([  +ƫE`[[V̢+a>|PypJ9-Zw2KhrE$謒%N%LgPP +; PUTMhwCics 򂆗AU9xIq( Mˁ7*(z6ǁ]<$K +񄅉QHё3\P'k(VdHK˞^[IөX t`lիq.ɡrXpH_%znpM!wPF?TGjrV)UXq +$xmE Ht"ae20#e_Z) \!RX(f2+ӳCw.#t@~B79?'G ɇK,4Mk4:gȔG"dȄtxKIt e|.+f~Z@Z! *8{@ȿD?b,BPh]PqYP +9B-GajV 8w wFxD,T +~[1%@Ɠ #* യ@o ), 呝R, FJl<E 0Er. +J ߨ8Z +dŗn.:+D ," 1X3,6QpΉˢRPzq0?٨|,; }A,M@y:2bZ֏7aǗ[ޯE2(qTs܄yb pR.柇3 R0Hj ;qS [)KY +rR/g\'89!P;"pQ6jTiR;CwMF +()[³,Pő-qdjÔ9%e(@;K g{ŁT!I7 x;+=s8ȟ:5؃0 5*">,?ɺ$!)=u v@T9Uϊ3 (˗.G|5nR: +k#m(aE Z2( GiYJaj$IL[ȅiin8ڋ݋RO$4PG~d#X1-P[7ʢOҹq ah +`^+IJ>MfHwWnYn"E&IUogv6S"0)yƼϴIiJ9]&QJZNǪd.pypv!@s!JR| +99hց.~[JΟ/#@AX0-z(˗2/_nxg/#`ڭP +Ϯ_8\ZkRer&a?aYX9^x[؍a *" tsa;~$ Zx/w" +* 9T7T˲&xuҽ:\ ?NOgO}')k_?gS)5x6,Cu?bPNbE eD=(?O){T§fLd&MEz  ",fAHX %H) hf9jBFfhͺӁ9lT,kkSr3X\}|9boP狂!^(誐ìQJ|.35cyxբ#V'B`oOp#x 2/_|bAfʤS6vҝ +6}H XI$P` +@A Cu p?0cHM/U<p@R* a?P>E+)i1]jJFnguܻwzRe +7ݠqPoSMpZ`o\6TA2$HB 3л*){8%G]yhai)Y + oIVp3 o Ga M7=I@a "[d[?a5gVDU?+;W;d}D#Pkz94] ?ok8B?)j=)}z _uJ-PK/_O?$ԫYOb[ҧbb6V3.L~!@Q,6x?~Tہ5@٦zM|O}6퓼Uz  +IӁ^;P@G ܪ>w5k8QNyhNh #Ek4M `<%s>JiL &E'Qtշ@xan: ,$!ׂlNbIrdխ䦖L4a3쪩z54SKaz4YH6X_X>:#O!MFR#lϻ/ Wϳ? +?(>UK/_]yHaB:jOBhl7cgc/av#g]Т`h +G0{ (#Gi]O֔f+]b)fH |3]rz%>?0nS +ZkjAP?n0d4\bu?F[ KnߒF3. " +h0)d@.<ᒸ>G= ۨ,wEln̐Cۍ9U_y`< €D-HGp^psE +ޕ*\J~j c2K+oY4(En߂:C%.i[{`wed_`jJzaUC _$Z2fΟm#LQ @P`&I[BI.x"qߥA<ʁEBvǖܠICS?imP 1ߢa DŽNvsrnҔa +J M|- Zc_ g[ڗ=8({eD7gB>玴m0|`tW~:#,'+~(Ekpwߡ_Z7 +O*@KWW.2E +_<Ӗ^\b}A>ߎ1|du*-[8Hb^ +Y +}M^D_'O[??gR?{p']kDɴ>m((A_7 @q-s;cϟ\?4.;"_ AKW-Wn^ɖ +\,c~LZyUac$y|)j#RL1 + "k1ߋ4  5eUם +ZrdlٶW@U:nђg) +b$~ ֏ @ +tLxbD G{u~Ӟ:(I!]0 o<|#K$xޭ}aPO7 +/¡j*#k +Qc 1W@y#PWJ2@PB$9?&h)llOK +"D\ YEwz7ǐ Kc% {Ob%qTo<J]+@ e4w%@GJDa"le!ED #Y }(Mؾ;{ً/TD +"RPU\Ԫ< 3$*gP +ߓ( +_Z +KcGdiU; +PS}5w4 J݄)hqc rO >\u0d6oS?^@4IDu +9c=y4P0CY% hȷ +5;:F66txu˖%X +/ȕ +r9uR +XC/˛4Y;s>602֨$=)E?UR.2;v +Y]܃6v`A8ϼ vAKъAh +A)χ 0K*e +,WmxAA `cR%@~ +'#K 2@`cf0EZ`Q`Yvͩ~vcGSڪׄ +9)RO +pAp8;ҝ(x @%U2Ag*%V!^>65UMǵ2ؕ= Y"V䯫ݸz|Uv|T\BĜ|zdv +SzY +'ݐ[8I# +yep~x0kT (Itv|{J]b+LѮg ǣS +N#行 +.a:*s&!f ;1 r_r_^]|t g +0SDFI7<ʲ/quoe$4|":<$tS8"QEݐP;P漀}uH +.@54X pL#ŽLqȥZ@ 2A3(XK +%bMKa➟{??X ՄsY}} M?s# +{kc- ]}4}~\\ PhFM\bu +d-dsO l. +f+(eOpaZ8t04k?e33[ñe}JF)j)FA%3"HZ`gZQ9Ƌ'0G|[@ }RPo?H2nR򪼷 `M`ɀP "M`XZTpJY>W +;yL8;qc %I&,]kGQhɯ'@G SD6K8p9ު|G +T~ NA4?yiHM󧨣isܟ^pf +YPėe:5GvֵAe:{GȪ"%(x#Ʉ- ILI\4,2 +pFr&Z.ǠoR4&w/[ +`Q@[L&#SD΢A!`ט$!D߹ + h, WBᙂj0ZEΜ[!uS|Ap|P +@#$- ` F.[͠xG2K_G +??p@X;؋VY7< +e=f e{l:,N K.`qx2Ç~0_m p̜dC.Te +{(ʯn)?*K "PK N$l:3LՄVor(M "䌓%}&|^"$ ~}aI|EoO6Iv6) F +*gfp 2N +Hi̟ۙYޭxgۢ¨':?Ap1i8 + 5A +ol +h〡(R%QΎZG??.[bmXQ + +>qsObz#G^1~CjΖ#ԨqCUW2K +GP|BNKˋqFmpڙ%S)If2)_gf#Gl +ݘWRꥂNox0⤿a *] ̷G6_vPOd:K +?'s5@_M383hPtZO"k6_I$2|װ@ӽD 9(Ǟ` *m؊; + H!(- +;N?m )*LAumۤMM~u#SOs&tnraTu3 + إ.):82ړOigrՎZ.z$K V31ad{F +e~?IM.XhjÖe7!S< 2x"<; ] P9+Xm_ I +2Ȭ;}YQ _CC^+ Nz~mߦ) fe2!*T_߰FBRY@J7Zl,l +b7P80OUz컃 Xc%E 7 t[~>Mױȟע1ysgj"M;\X5p\ *0O};#wI@T~CZV;.V`o_ ~?-(ZQK!C[CJ +)6.A +IװE"LُDIri(}eTsy-ܶE8Q:fbBm_wv)znQ @ 1d +ډ >yJNeM"(\@pc(~tuj39"5(^@a>0|륌5 +X +M 0;bnBKy1q6(@X +:mNсLYo A W(<dycPGٹhGQ_нM8<'kT3*k>S0r, P`?0 +{NZ' +Sh"w|GM TK|s5.~"gaį̒(]zoYe4Qy_Rn5WGnaϟib<Ҷ?'Oyqۿ|!.DI2lF} +1QވS=Z +O5,EalD4uB+ZOMy+-T* nf6c~ V1S'vSk7լP {ēk1[;;!6ݖڰ#SvX:9 +Gsټ +>aޔeG–J.&xz y; +3.&@/[R +^4b47ulni&^@QX"@ގWBm +bAd2U}5]>SE +vd.ȁ#$E?_ h ઀@Fks]#_HR^6Q + +,0&eGɏ{|'xS!Y8IDy(sVDC`)LfсfjKT % +ܒ>ݾiG>xU4Gi(b]>-{Y86Uή6}jP聸 +P-6A?3F 3Ok|=I3o]0qmIj*0Z4̟X `ˊ@\mNpvkUU@Ͱz1&?J5T0c߭q,Bό)-nLPF?EY~_ AC+_1=|[Z 8&ˬr +hBD9S +ȮPC +дO+ J+hWdkOsr,4QI]8rS=BrNP+<'{}0B%Wjߑ! +"D v[4`5?$3>42ΨL)ܜ%T['>9%=ӧ-֐ͻ$K/+*_grHjKl#1B a"?) ++6?!,)?nO&WηOuV |&C?aI >Dv@N-&wdź# uJc)%9^@&UWd}K: % NK5'&Hl~Ye;.2ح T{؏R%50<緾vO?L +JS7 "Aٮo@^!m+oƩU]O~v +ܱjeT9O`{Z+BVhVe*O 9QGi< geѹ|nk7B1 7mW +h/s;dj:=B=" p +E25\ +sLYcMOvT0.ʿ*sm +*q\ g"O +(+;@ڮ4oY +#Nb^gY#h#P`trLBR_uBL}3(jl l1vU@Տ?@Z`LK~X˄˰ +;-@nB?}Q ɿUEr18?T3+/tRl1%@qOK>g9k0 W`կ8V>hÏ]C +o +.x;OsR?%@FےOZ9B)Ju|/V Xs~w|xkщ^Z}P3Q<.ϼ3"1 +ju |熴NVsrq!WiA +E\﨓 ˚p1GNZ=P;Kla}l-QXEp` +DaL@0p`>ţ\XpT,(}'*e?e"M3؎] (Ļe4? +a(\BB~Mj96@}L $\>Buw5dކ(@#O.[ޑ wAqr_>3?t5om!E6/^^m.z`eӉFPᰚBkMer5*-?FILiFP|gY-#}Nv|Q5DK- W90D>ZqU@jw.`V}?T)0a*x0Fgј?)Jd/|mvDptvLOf{;WG1лG% +XpNAE_Wݔ\ݓN38 &?oamZ,&lçлQ8.X`N( +9(+&e̍1,+M1He`r rq8D!x|])ɍg c89*ydCD0 mbz_$ +p{#8Gťy DֿA9Xa,Zaіd'}_ObU@i׍-WeR;;)z?6ܼ(Rc?Ly9qI?t\Z(%>}Iqzϯv  ,rw @o П +...ONΎNolls1sR=^}xh[xu'GH#X؇a{{&$ScZGyAx7̟ +S JŽ~[!Ao:ަ&AbQbL2lV24HP %[Dgfsb* ʄJ /"e僝D'%.>NW)>҉Y?%Teojhwkjy"<3E*7ޡ,mn +INdCzFͶ4ꓟc{ +KI>D3* eGl'"~ PNHAAT𠂒f?* +GW[.}.[:.[I}!ο_;*gtWr_>l(??x +ӣWA+qWy{#'8w{׏RUoC:A0 "RO Ph>vE0Dܕw% P:@'nͶ?oE \~NE*#?o?n0Yk qFw&tIU?$)߬ɚd~ +AB5l!D4i 0-G3lO +[K4^>,81vR;?#gP@ +E#sJ67(ݵVz}51Oq ."mBAM١ ~Ij7*Qh k]y7Jb^nsގ˿7TGgw#?ߨ G(j["'y9 fƚ]C WqҥA<@,rhLG(߯ z?ͦIPB6D1fz;`̠ +zAoc3X$83A,JiҼmFK +̾=uPvx?RV}AO@Jwn!'1IxfF @oUp1<9 x+B!FmACa9qˍܨfmΰ\7l#%ѪRK ۖj"xZh?d)>ߕ*uT:~E4W(ff++]|:0?_I\rX\DOQ  +ombŒo:D@}b^xA|;Oa?@HFPY.wŬUSD?[* +h |L/> }FP`T|j89V#́_͘#ESs_ &Fs2'~jnBAߣUUhٌ +L;q$a^P5GrawQ<ƯW۪), +1>C,:I! + +Odu}TU\386iHE] edlQo2Թ +g +xs{>4HAݲ:eB4K9p +\)@1X@Yݲ?}58^ T։sr9OF +> ,'(+nEG p. +G>XZHo#xC g>o%@2b<8 +7qGnWKt󹎨 <cnj9ޫ^qǧ < u4\=r5St}ly0#<ःBGgi" +qx?_eؔ_Uk7 Нbjm-+"g,!@T + z ci?lJRl%A)*Ye/^pqϦo_flh[{UMvKޜ}9[s:F\wiL[j0ʨS2q^bY PFG*% +2> %+UH U4npT Js/5ќ*O7 + K\'ώL7E|F nҼZ@ A?B箛G sXp'MzdodT]pS\xO\Dm@kn{5D +#HN&gg@PxfGq%dW$˝5D03J{䟵It'Pɠ⪙ +̗]p; !0Mܸ~3\ + F paRZI9k TkV1IW,ʁ֘ +Oȹ#2_篻qQJ\zS=¯Vbe`TknH@/(J4B$5JYIt%u*QȒ)) OS*-OMSo./ˁ`+sf MeGΚ2/B,n>7eSOǶ o6ݺӹuVuVœ5[m]_?~jT/nE:''\_C|Fpai"gK|B`kS]:O2 0;yÃH"~nN +WP^2e29\еA#:R~oz4"ݥho-}'Pؽ\us)b<1߀ҿY3S8z*epQf@3Ç}WpB[ ۉkGz`[6aƤ(!&[>EY|l٫ +Zo*^X +s ^Jts@S薝Д[{L֨0\Ds܆@7uoA9vK:X$VVSGp=# +dռ gn2;ϛ!mYO +2vUk"N)@$WRwq)@| +?O;o,:QF+*m$-CFE z$tvUrgBn(@]l8 } Bv{\3:PXnzp(@fhH6W,pap͸jmN,3? Z$Qsy׉3&3Q +>H` ._o8l<4 7'`H0ې%e :-WJLLA@ +Yu?_J ɃY pNJNb-;d[Bޑ +P-g7:!/~B},)w,^2 +ۧ>\ +ϸVݟJ~+v+F(@EYeA^omxxX_l};i\'<oa 6?s;@E (`V:q1)/U_!j +Σ _>I~ANW ޑN'_gv`u ǐq|6M-R6qH<a Ws.L7Unk\R]-"|-ok8ϥ5E[PM`t}-pg=7A G +/Ϫ"'QtZOM [p?sqPRR2)B__}COԫ;_=`|L^yo{sz/Yi[:yvO.?:@ߕ_@, ŰQlDt'P`?- +9>8#{- nv-XR=H@Ի1@][)!W?5b$ɠXPd. 2H \ {ͥ(C +`ykPM63p-:Hb2o2g9% x4!/Gyr%MIZp]A(ϝ.ͫv1v鈽C5o[|ܫ`v ?#b +ׯ%NA$7B"  ע6ձMD_}ϳGrZGu.&!JgwW6ׯ_onnm,/-./?߫kG>r9|_IOEHʢ9@`g |^; @[lcFg^sL! +`!dr *cJ$vS| \.T u +0)j +kE@ߦ:7"(9 +3 :g\\yis:O\P ~G9E38] NrXBz 0nEpE +1 a 5 G׷r*r??Q2bsu/#tF O 8d!H`d}(NvxAsG9+ +5vGOԝ}#BqT JOڇq~+I.R~YP9µֿO<=E7% TǮ5BK)FYt "j\P/#=J)B=5֪!"(d*LY Iq \.)O1= 7].)F +5TY\Ï5JX8o%9#ApBJߺD%^+]RvED V`RjY(pE /[p GW&d Ej3Wo!4x30f)mZT/(̛/,R1?YZ?xJ +ж 2yiJz +x?'M +DSECM{{@PjrS +m+2$ +)$ +xq (PfFtݪQh+q+籰K)\~Y-oʭҭ[9?ʑ7G_:Z@qί\|O=wIt  }L>9*si +((?LD-2jZj9"> +' w`\e?/_,nYݾn8{%] y^^NۅӒ/g2`p C{tt2&[ L |+OSfTɞ$a$0䙻o93B`6~",g15,J&@d2  + +:"@T9 f8`+6Iee{gdCSn_$;DXz#8?p/+?a*{Sp]I{~n>RQ{g"m]دTJ~_%/xk +OFbkoJH]Q ܰcu e +"ϻQA|B +T^ͮ1$+()ڍd% ]~Π{!l{bW?JıW4Ȍާޝ! WTEA{ako!&ahyS Uy +X!kG) +`4@iqꢝ6@gj&Ѝi*lEbQؕKGuHCx,h{lxT)70gbYhi*^Uk vyY `4O߱!J#SlY_rU߅TT囂`}Yz' G^F7nׇslyŒ5Q5GQE: p)wn''yj *ڷʼn +XIgkv 5#<V?'?e_ +C0.7Iӛ &D=j#X'н5cWH@˔U& d(0bW#+#)Żeq?);X 2VP #rUk=P  +" CWG0ǁK _Eü)`^Qjα&m`_T{ ϰ>'ofUF%#pBQ,vU-v}th9%h셕2ogU΁ ; z4FٺK0#p$B?N\Iyt ,T܎+SeHUPh tyyM8s!7k^ry`\[J7^2/F4 T +ܯ ^L jt +ܢ'^uY@?5l%@L{gyj*Ϸ(./\wƽ <_?dUku\ݔ=.w_+7AU:ǀ6.ߢǩ{@zoѪu9`V*ZMq? +'觚jq#C +Wr-ډFh)[B-f:.inK2tu 2?X9&e M߻" @y(j0_Lf<͂`ŝSC 7AD6UWUGfsG +*0,nYݾfx< OVRT_2A:X# + +}45RW)e@s,BYrxS +_#(@9\8, .7=0;u< WX CǴH GX6Ra9:>R!adC*PSkrnWIu%Vr495գ{_EvCUn-ȍEljjp%$\3g'vrV[qN_"i0heUxpNH +Ҟ v / /c rp4?%mU $vCW)d h|7| +J!xQWˋJMlot9Ցi" +&-}  jAJf?'Hk*4 PY+`("#AEr‰@h43X%JICW]Fl!, +V ̱7V@ä3Ui5R" nAAН֬3Rl)~nD!:W1z E(Jt >%/%ߋ|5h?꽎!]H\`l? +? -Ɣ69 K= !_h +bԫ2y;ZjX-ISAvo +=Ye33btmy gA(j6uBK}ŽިıaW80Lbfa 0qƃWA!0x% +1% + 'Sfv5;#_Mt0)$7/NKz#XV8M#-UЫA$G) " -IJ\}G.·RIx,%q \D zC~[)2!pCr=}By5 rH6#~)#d@Cv%E@#7hOFB8$ qbDB C]I +ZJ0&`r`3@m@f9^m ~&Lܶ靖m&-ʦ5OhM]MGɧc^h+oS:ؕ3!M90g =t1-w@'#=iC+BT_ +G([v&6BS^S +ݿTi=\Po4/S,ʹ#3[vfZ2XK(Ab:@r.I@jޟOB$2WzR3 +Jb{{ ?nuh$~3J/LIB>{ YCoYzeZk Op4ԨswID¿!̇s +$W}Zg0( h +YYX) 8|nX(oiф +8u9qxNA\K +ܨpL̓[[`f{#w/PrڣiueX:qGܭmZ}2< o c#p^N= +2.`„4`7+'L l ~@vbJ32M+R +X%(F0&E~sg&1UjL ,fբ +xF`̐nB,\a-xodp $h1&2 $#< Εf45,G"@>-@8)T-p0o>`B"PJH/:f"o"޳t<$b!`hpTf?;A9iΕb9t> _,hn dz}8m;{`2 iVBO܉_ +Ϧ-ph;]H'?& l?$c2p +َRDž;:Γt- +-@?2nAZh9rfOYS#T-_-8}>g &?`R.;U?F +% *ak-92 œC+2 ^HlN 0_UlׇU&㷓 +E"||6@8Ji +/ɤgfq _ouH!jۘՖ}?}x1vz NAI"J#d$ouo%r3!9D;v%yA'^V/! $ֽxe2N]Ƶn#Cv X?qPZ?yoCH;$Kc4KEcใ|*3ՆXT5KcU{Wa!yCӓ\a >~_D<ѦY_j.Pl'S _m*Cj0z"Mc@d` +sӦ7,4`!@81H&% +F&U>۳Q~(?!lBYvMlP+ Fs+{oA%~$19r G$kt5w*0[\|>t_l֖p#Ћ +D.pCV {MRn`pt`WזZ,x%y[ Hz6RL՟60cAl(7Мgڿ)32f +&G, `S1mUȾXodC},ۍ{6j6w}=GMmI7SU!DHm͔-9#!W@k-%dVPp@H +U\͂>1+Eφ3% E`uưwuxr!vP4/{ǒu7̘qpX>xW$P<5ŇEV|D +q~ +#:~#<:dL&_ f'z:Y(X+@-S>bɨYT Y ޑ!(-G5E)fyb]Kd3c(q3(KD#7W*T戱i)jPJt4T Кtజ9—rbY 9Y9(eydR%s~.U9R/]4I'6WM~ T-CUT2&wdž\t\P`Hz+av ˼`P%% +"w e3M2|in)Ob~ #N6uhD`mXf@&5tc0yhoCGA8b"u +3dl~CkWGoZ^ +v1"L(h LSUie:Cԇ5X^U@›8u3o YI#nBJO+.OQ[v# VWp|k=)8$8wHzw.5N(]y]+G}݃T nZ- {MLs%1zhQ +Hi%>` + ċC9_e;vĠ;( bYAh)']OuPVt` l>a|kNzZ?_HS,l_4@ƕv%o,_ t\ۜ]f,w& C ֥t¯`5Ѥ4/m6\2ٌϖ +<^eP#t ֊@Vh3= +'f[16O]B*#FFCTq +g"xq!4]˄a9p LUPSeeNI +8K gKXm>9Py@NCUpr%yѣ,._qi + Z-gxppBofD +ۯpYKVDr@kװwk-!) +Z_@Jm4Z`[(&! (J9P*S!G&w,96ŔWwhc jټ.Rhmq촏'i&k_0Rtr`ɖdj:*^tAP^`ؠsma +5&`$.@_r#iN0rv[ms`)_! +HkdOP!NՆƳTIev*صЁ;h4wl'=N# +e`uaa^`_iqLhʥ H ̟֚&\upv/>B~ϟAR6dg3~o(gCYЁ9 :5Wv؟U`_\\Y)>Irjٔr#$$bwϓsƔ +EH=k&Q Ͷ8`^$?CK)s;xULX(@MI^9?r0+$;/CQ)A뢑 sg$ +@ev3j;G{9 Ќt7J--.`2RiB @8%@4k ښfmB =k(Z2KUL5 OϽ$S*Xn E#KK>ޡiR"bL|]@W_-"?dL;";?'WV ++IwG]؈.g0r,Ul*@Cx1x<?>x"蒎N%/:Q 3Y +m`]M2 -T\%l-jyKZt,g 8lq`l g';Lrt@3h0|V TÍjPA3GDŽe=B[3g\%@8z~d?qYL +/+1GW@r!_;XG6{dָ8!d[ Q ( Q=u^_}ߵ6뮵Y(JqY٦Iv:M5,S +p+n ++oޝwfxH9 U\\ HL^:Dϻb>A#?wjx1(f^4 +0+Vx499 +)]Z*}7^|{/ߞpTI )(@ +eἻ +b#4#+Gyb$w'ABJ %@1 ][D:J9IYheM4!8%aVs}C/^a(^:.Lh7 +P| )mu%J۟bVNĬK[ $շ0bܫ?/-w(2gp4ljqW +0yd?h=ʠ5EkmvRsWI9Ђ7 +f +N[]nGRXD~ˡ&n`Ih0#iy%m?O2FiRb Ѐl6=LpU/Nb"yT]RYJe$7Rd>+.{=쥾q!ΦB뺟?”gZPs3G~B8D4 +2'lSXgɎ`DA]Qe]|J^L;;֗*73>}3_)/LG(LmzZ%G +&_R)p>-3Ђ!k$`C:Cs^ogoM~Q70^~9'oN[n Ĺ"*@ޱѫ;vE:A=d@fBJ@G2@8:ּ`yL MYh\ +6A˻w^7tMy~j~cط[2ҖR5]Y_m2WQ  +h0ih;vx'aX-〣t<rR'Z +\u0%GRH +AݽJ3PB^@ag w {lTF3O:cq' 6^YLsGZ=O iI= //Pr&R0tSϫ3:5,)i7s EWVtn+h;0,z + +Mҗ@NoOкS!0ϑjPm2GO{oRji~Y򵔸>2#=Ӏ +ehY?)4 +1#t'h(aB{[$K?p~Sy_ſ(@z%CZ&,g:?5KhT*`+1꫉6Y҇ +`V'y_=(O~aq^A7[X@b +6zmyC~_=1 +: ko_)h2Z kM:hݹ>&Yp}lsRY?beXujRJv2ly(Pd|XT +# +"#h );0dWjd5 8> +IpR4 +Wf&i +4lKq?&*v[w$l|*.O m7af@E$UXgو &eXMh{8H)>*@A6G,_pΊ+%W<>㷚Eif] 1?/m@")-]˻S~tA(o*Y˺ȚYDRˠsD9hf;g+WU9yGxhVA>@k1^Hc><0C i7Ep{S+~_Az͗B}ޭ~2 8 +Y |@kf +pӕ +m6Q tYD3YdvCfog `?óY̧}JҰ̸/gQ hɈB +B@W0I'5ƹjC1qQ :+.IJMA%行M7z?ޔ*F+y."IZ~ +LH =rnO/൝8,H`uĄzr0|븒09/3gfwYn$t]Jzh:M9^I\2|ڷy\Iv +ۧ* cM(5Z SPxef{Ȑi"%V9-y +y…s>OV|؁,,?ICw3n?k8|!w +懤ZAuB&yd/<vD!9Vp(įx^BS9ūLDS98)bp@(­p~O @K`~MR$:H}͡T+;埞vsp6E_abE6QT +G +X*Tϧy%U);hi=8j0), +֍ЦP))K}̕#/z6_ ʁQ8q]H +x%;I7%$M)­p4:0 4*NPmߛ(r[tڡ葝1zkz_o}x=VWx=wU+ + ~_]ȕ +JMcM|52Bq\Fu> 2 LBcvkx0:5ʼ7C<*fgIغ$_Њ4)Րg>H)dJ^~$Շ6A +OQpkClpuW +%6S O WWYͤɿYZAk)jAQsDI",,\孶X8Lσr53#?U66\_nxx*XN)}xiO۟]cuѓ5!_gꝉ?}?_u7O7O[ovU@z,o2{g֗ + +WUk0a}?eY[xk8zXX6 N`,eÂa/>w _2ɖ@ @, +e5)49щ?fÍzb<C2z+ doY OЇ+X=>{8?єigƭ>+*iINat7=/sPl&VꯇNq +`I>nI3mOV og#(dTv7Y }Tv3&-i5r-15 ^X?Y oz{O'ςK*.Eg#9oϯPD;.tPU ai,TZ>9؊g;Ř8 Ӹ7Uv͊m'Z 9D,eiPiAxrF ,#*Ol_wR.R&kl1K g=^ئ DYٰF} \Ԡ{CL^9M{2t8~R|$4R :54N>W.#Bz +Zهcoq֚bmsT ix{_wHǿ>i_s?64; ğ&q +HndzQ$6'$L+EI̲$`EPD G|5CADY=Dƽ' ?{G{Et/Sq3U,Bϕ<^g{^=|tw48Ԟ!ŜA;(ʌt:5# 28X̲ݬ-V>P^TE=}'U0GvF,Yexbd:]4GprfhW Sl]q)m8K㉌ m˷Y˦w Zjd*6cLLs-mEa"6|>9< ij(j^dfza1 ]b,RA&h1*mʤ'*B0AYT肛˜l?Hn?~sfi)qS +GPwu̶YGہz +ÿyA_?OO'~?p_=`S_^2@( ibC{c! x;0L oyBzP!Qݪnp9*C2p{!F 8(U&l+>jSKa&/8L. kahh(Uw?ȝ=! +6YVߢVy_kH +$e ,8~x}  k `-KSQX{C$[/ФЮa *#) '_臣-z&u(jv0`O$a[ +-O +.e:Q5`5dzhDժ؝KJb +u : Pv3:bv&b StPbJ g3hʙ},3|rF+ h1Zk)?zj%Vg$kBa -+. +S@* ;H!}4)%r>hQO|{)7RHw`|(8(&edp?VnPMM:'V vkY2sP¨HFVHq +:~u +w?,GeT!v[O.ki?=TKu9Ǫx`{m 2vKr: ܍_J/^%< hg=狇E7 `ݪ\'e=8k>Rj^{>s2b*!}^@}v&c=TSلp\exQe?ѬG\TYD0F@ +OO}P _kUGr*SvjN:բ,/NI,>ָ:l +!M#7֙n.9cKgvz +-0kaTYl\c5?+R d@Sec¶%nS)t +;@9D9W~|C@X9eNaϳ]HXyp 'MrVSB$=(pA +͚(hÂ"Hr4vKE\궄P\iIK +c_I=aVƢ{4Q4CYH]Fr6[.,sL4&qSgO'úl!`<$Y*fi^z@_z[3ߪdLc_`4R>]* 58`Ɩet6wR{Z>ĈyN:)(yηỸY gb/J P@DpD^t7f1oZa +ɏx1L2,9sHA{|60 +)Dr/~Tn t^ +I5xH2Atm + u3O" +/$&\HNM},r +hC/o]pK >rػ}އL +o  C + F/<ۤ XRY [ `(_Y'V! 5|l9`R+@ m?hV<$PIWb:9 FpJt?GM%rH +in? P}")ʩzP+4I> +f(@)/^E C"h~G'q2ݑ}Sm^/) +d TnE"FR+I΍sĶ5s2u'tJ +a&ZG@ + 2#WhscjMU>qzT(d^Ã*[kwvkru)x!jq5|v?sTZ#p(k-hY<r +*NHij$z࿙M7S1:R[w1uEb$)6wc DڸX)@-ف[P' yOHIF +&#MQ&OLh(0D3+ERgdƿ)˝>XSDQϓ <#$mfaXfaTy3x +ɸa@dޒD +ޗ:@$ D}ZyS4" + +6P'!IF.ǵ`Oqgs9'f؂ߢ_Hg,>͗7Ɗ%}tl +% 0D9+O3_u5R}.R z)@D(@ 18 +'xh醈G"=dm|~xb.mћ7e1E_/7xCfGj>7zȄNy83|PIa + gH>#HhR&06D:/jmpEVP +Ba[?#I>>x˯ *?`i' -sEU:7ἀ(7V/)bE '0v}V{Pɿ}U8*gL7Do͝wic~Y_>/}R#6 1b'4sҁ:PLs{ޕȟY-шG!@b(p;#h5?uD O𿬤Ca uK#ŒOU8FV? LJxvξ{j"aijNK+Ԋx)^nO}CY1ck\l͙[^&#z6aaK(AG%?e?' +1!"|:'6Hx][*ig7Ϊ#JZ.a$Qq(*3s +u2')ϰMmcllOXf'`i$?7So^#?:_ŕ +_Vۀ?VB&&>K#6-,tZp1FKpPN>O?/O"mi_%zOkjqLCgmM+r4=qc$f>m<'V76QY"63z?J=H⻏|JP8ax݆Z +GɞRe{rz!>mheOO݁@2a*mG!igN'B*`B>)`|i +"'nvpTN Swo8cR[ۆ0+H1[ + +؛hE?3ƟK_[.4Ov׉ +E/e:&L癦hyOgF aұ ;ާ$}8fE_/7cIc9R !b&F/QDҭ%@U@{@+?u_ V9P?EO03 P02d +h?uG +8$$fj:Nu9a:$b4TjTÛ +u1-UpKH.bfxhT|_A#dy 1\g-Ј?L, +2"xeV3_}/C!)RK|<6[ϾlJk~4ۋx)^n?[/>CL">srS_:'OF@ȓf@&M`A`3{dSiKBJϸg % +T۵n?BmF1+j72:s@. /ɀ.1HiΞ# p;>el?J\՘?b?BU( /|(`q,nӨBf&k5RdezSVt@ +Qm4τAot>k%)fG>-^A@ _ SbYDRS@N<Um+V[UeC&ßLh 6 Վ[)wB:}})v:l +njNZ 0R[3k2t&%Չh4l IV tpIL +,`0^$ ^'*]1 O94-l@*Se@SN$GggH'1# ?Cf|d|~ba@>"dTY4U,9L#r,17ґ}˘!5V? $OU&KgYUg+56hh}$DZ~!Jn_dU |) s!wJT|(g + +?ρtF +'38қؼ~Z }^՛?Cmn7G*Y&E_/7$Q8ޣv`[y x!?m'L +~KM33${?"X^h?lf;=J$-h2@&8[pW=S1QK%BÊdV"7@lO١C]qW5 JBmqd6A /:pIzv +JS +U 0ɎC(]k&((?鋧's5CzD}jp;'%V}GѮ' ^sM|6؍I ( +8)@^A +7 X$׾Iɳs]d+\Gc9Zol(H!1H*xGU +2 6~iEz6 2<ŰfnrN:PD]$rMsK3R82.yHɚ+ 6#iTpRn҆CIږT^Ӳb%![-/_v3颉9ɿ^ ԃW= +n#=s' +|_1͚B4qd|Y֘]) %KЇ `4_Q>[-(! +e染?# +]nr^@j\"KhLD /{+x"ME{ +6r;%r×ʥ(GImz'Si|-GSby[K& 1UxDfJYK+fK?GhMjW!2'J +Q嚪 ;Y02^jVf&&\??yd4Ȇm2 gS!r,HpZ!#c/ X!#,`ΩaHPr]_='ucݪq14_((4*l0t'|R[CB zi=)`&1 +_9[d՘܉l{NfNA-Hš=(3g7 c:3zh9%q21-G${czq:!vVu'K_Y;S-UcPߦT| dgs [uѴ66#`I&xḺ6,~ҙ̤ `ѷXNzWX*:E2OG +x=/>;>}u4~:\-02]zΈNߏ?^2iZgH:A4ۏqLySǤн#4eeJ)Թ| +#W1i[6J**(c, ͎mm5OMsKmlu?YiX!@!yt/Jk&_ 4#i> It}r=,Mت% #| F\)J-F?E +Nѭ)KXnb +@hlJpbk4C`aUs,ǖb4u[X!`DwM+ +0 ڶt^tm%>lIx(6GwM]i*hbg@Ô +v[[Mwf6aN#~oאK)WKCW^KOkr GJOQ'lRwLk g+ jV)$Fjgn3Ty̐8դ* Ñ׫ +Wg:J^iThD3\ l27[)wϼ+uJň3Onp0D7l,4[;TxQ:Ŋo9-z6oe#r+WZ\Q|!uc*?zJ*v/D +J,% 0g9b2c9=@x)^no\! +:1[X"ҙs:Cog*[/J +&sKjqǖ> Ǚ-\XY(`Z-UOQE߶%TQ*Z[g҇֯g=0^:\>*OG,sDӒ;"7砣tsi7$3gf j,9pWӇ$3Ԡ1`5w&^VəK~[Jdxuz P\uz;o}> g"GdGa)e*Y,tft)F,Yq$ke1Oyƭ9E +? +c &bJV-f,GJG:#1{/ X >ApO#]tFΟZoBO?[81y~NԠcuAߣڨ_5WFiOiUѿ% +HWkcG} v}Dt7j yA{lMy +КP Eh$^xmQ_&lcd1Snùf w`}--hs1$˲SۿGr +_w1QYn*4"T9F5.5 ^ K3p:hx@?հd#{R*ŕjF6<)@cdb%;vp߉Y>91:’),67 J-LHg7&ص}MdlW9hoyh " \HaM=ٕ"oSRF@8Vm]t8;#S2M.;oF8Vz=B~ #\#wt>N_L* +􀬻{1h<gm<_ +[X@3|6Uhk̳t2)l +0PD*U{ +]dW[|Z$3π +Zyeԙ(@yZY4xRά׊)D +Ш_`DqU@e5,. DtI.~riNr.u( =!K &E`5H SinAqwYYb62sfCǨʷdW6'hM${Ow7,W./.lMfըS-Lkt>ktI !tqyjÃTcYwwSh{ogtjPy2y+TDjBt3*;Qd QIt꟩t3IKǰX ΫWK?p(IJ4t ଄Sjv:u63Cj12 J8"KaJ:?jyɦ%u|VimYLk`UYh@b5XyЮT.R,SKXv}S/$0YviIOo+|m)sc +ơnz?ڭY-J +1]\18,Cd97}yt )kRN +d4k"X, ͽk@)E)k(:lk!+Z_-OP!sl&l?uh@7֐bk3`c H#_/dດDw +){ey\y t3~mHg +: Nλ?yP$'h@p?לi|f[^)SZa zCzkwViCeZ_ZT٠T0G<e̖eD4otp + WgwwY +庙ޤ6_%Q=R+ڞph g˅+6rv|jܟ#FJUelAn +u`{:ZN5}>_p 9p\@:R`?` v#f76q%)A Hz9)+8ϑ:~}`|^!'?Zq tsq +@(+naUIQMWa^K885/= + CGÔyh'椈025-{8oCTC~qO⁷1t +EMn +6;VY@dE/L2&GϱLF@wx^`|X}?n$z?|?Cʃv[[٩?$c"pqK/|D_8˸,JD E##Ԅסk贍Mia{-(;:3Fl.9YKr - +gj[#( 2p03'*yd!8 `@8<4G`Y؈6r:OK?, +Xlvf RH%;g9`Y9,aW&U&mv3,(Pd|~pA&S> ?BOjI?dw<7-*.Gu+ +H"e3h"wٍ.Z˙߬>o?+eݝFgfj +ȿLc)y6),Xt?i5H{½fxqq\rUG +@_ +C7xqGUTfI:?G~ytyv[_W;?u #0I]>}@| 7 +jГ4hcy7 X1nVdrCm ?S'eER^_Yf_~b(0 6Prv=_>%,jvbGjNCvZ%V/TGBnE.s MU)Ji1JԀ$&֓ETl>1;! flp:PW+ϰG-.̊Ɇ':> J&WFH^B潂OQC OvP7J.N\+ax1K95Y ]Z>BY QD O__gOj]=kwwY[ /RDR +i}(a(eIMqtf@WTb0أ=40d7Tn+Jё>q:敬=qġ݆GFSdy5\^),ºQf\'Epgȣ4Rp:(b JœEPU,@ *R(f$ 2q8᧿;Eԉ%@ۀqtgyGQ@bzih8*X1L$eKװZi^`hQO#EO~l_"әvݝb9ΰZjE3ԫፀx__^  +W>u +78Euۋlaa__@]Q#Ñ +\8suNW&K 't.״ 뵐V_ogOHăYͼ_f:Vn7|9w:ÿ@ 7TO!@!f +(~% lg,@7Zn_(.b.`+~yhEALׁ]1 +"BwATK)d<MB + •u3}07^08 +#T+.MzS7VȭX!>)jww!Zo5(:kIt +pG 5xƤl^ +s|-#~wJC^^% +!Y9 hF /fcon!Jp^MAS0Y@ bAqX1K"}? ʟx?Hv%zʜ6 +#X9b΄_,[=y=V%5پl)~U푝*FtX{+qAmT([s4~7:UȑdYH-oVտ:귳^ +r#wHc(%`zJ)?Ud1Z/}rd`laMfS>$` +gKl.߽BSL ͣ{r(j;%*/B> ['Q`R:8vV3/¼JZn/ӁF$bCh wyPX+[q@.Q5yfo˯*F %+ BY;vvH;p祓т<~29J}+:_Uڍ|rEp40T + +T +^w@ 8KʥǾ2. ΡuSqB'w?3o[4T9\KKV`<%4 +]7mĿO7]6SPQ2Po6/G5ƴ~kou\./E6 ҙommhhKw(]-C'zWc\A_6/ ti .% +hH |g=MG +:귳wܜ$97Sro*~c*2W{' GiQ ( +DqiD l$$A +rO_>猟.9Ynn?90J|S^Fcp-sQzA<<yOoIECIWX§*`Q'3"X%DtvMLYVr)lW] [>)v (TQK ~~ TAR}Nt,03&"){mkT ݃gPF+)ZjSoK͓IU%@UpߤHۨLX 5PjaT35U˄bxT]18<*JljGٯO.OxBLrS#;G^z#y.JvxLŪk֊&I"K1 +ة8Z/WB} jnY34UsnC%¬MD=׉@}ۀOG%*ˠ5tH> ?\pLo z(jj/[ +;5$5>?e_ +!^vFK=#n5[]L[gΎOF^dOn("k^A ס ۇ2Gnt )ǭy:?uPQM\L:8%+PnHI%#hsDxQ,Ӧ&nxʐ-V<-Sf(8fCU=X3wYϕQJO ~;[7 ?ɽ%AX@F_?ĚcO 3,?QSCmm0NO'.Å #Oe $:RCYjz jQ%U((@ם4~Hœ&Wnb32B@# hߴG@h =GAHKB +–pͥS_S$*:5]K&=2"]4EHM߃\F=MP >EZ.}e + aMV{Okň}-#v8kZ`y'V-J# +#BT!STq9it +ps:$;Jg"@ v|b).BكXdO7ba3PPoZ:,ջ{t +ٍ.y +r;e('h\`xAKm- MVU <چm"#ѫ + x$ +3/54#fJ^s)g avwoPVCz9738dz*jI/QaYK)S +RNm{Գf]$dU %lҜ?Q(CC ?OQ@퀒w޽kq{' t#!Xm&Dyνqv@g57ّYhm-;`G+C+4Xʐ +D|" +բM^'^kH ea忩ڛQ MZ+6 %!Mf ĒY9  %̴IM/;@FV/ ؃p1}YZLY|]z¢8,|3CH! +́nxu-p_M%@c #gEN)@b5i/ۃڊmŵX-/vyzD鈻n*TGfsWtD*|i1-aٹUx~*:ȎZm}&  ٮ~{V٤N@ ͻ:ךP8G>ԧ\L.ic+N9ͣ<- +)SҾ`_?uS.P,[àiB $“=Gπ{)0-oDDЁ3)< UVHdȬ}SK"pŢpT<ր< +ig,PcyF'Ojp`گ'qcI/@JaRx4[jXy2z٠aFNG0<},mmy7_/sJ"5.h|!P&;2ɲd/F)ìvc"@+4X&]! +kr=$# $ +l=U4[EANo٠-}d/ RB !vr +00ڎf +˴7`*_޿H  +O0L<8+J<>}MuABXt.x5e"Z'3yXe!:ʠ5H[>I07k +ŋUf; +!~߀<&O{ؤ?I -}kv7:EaKHY# +:q(m$1]Tk_ښ' +ơ쑆JkÚbpNM_d V/X~C_@B\'@8 5IeWӝT&as? O~m'T$9ǎV8{ +g +yN~BO!z5P\4G(xR0۟< /aeIHJV8蘲" x<8a(7'@B`&Q.tOQAfDDcHv~;z,ܰr]* +/.ߛPb)7g}'n *bIg%~珋N\y~v@N_>pϟ EpuZ4_z_}fc.P:Rw=_N'3P +|e2YD1Hq?ݓ^/u|{`Yw +!Џ úK$…)x~"}ꅑ8ߩ j/QwZ)ۥ@J +J+U^5Bՠ౅ +'fxc1N%Я3MM(7[q3 gv +x_l`os?*Ru[HİX۬TLXsA%f"P2z*]@F }Z 3P + Oz~Ea_k?rX:H_T +#mgYh?Ua` +fc96Յ_tւhoSz61UÕ2IkxR^ exBAPX@Rm-u .ȳv2?fB55U&)h~@{RK_'Ǥ-쬝F7sr|] Ώk~;O86O7@L/F,&IS +1A+wݾW1^G]Cթ؝7G{ٖ7 zL*yv̎lPy圫h9.LNg>CzvZT%mz%Sa>X;x4ip(L +buqOu1FۤT%7!39YlP46@p*Lp1EtacP%BǏFKH,/z!m+,PHM="M+`=B& +v7Q)(v!ۭmw*57ba⡴7ôuغWG BwW& LPD +c4`|@vKWIH0vOۖ# |1, +d`:YFڳ̳(foOeBQ*OX^ +JWD.˲B9~p cLe(#2 Z/:cjt?G@@ @foo2 ?`bek +h-) +cJ +La;*ȹ{zEB꣫Sŝcr^}5\ffP矺F ~ٿ)@$ASAǡه=bQxB562JIfIKD|g~H >28Z I=2Т +@Ex0'SY/X-pH^+*'C> _DZ(G`aXxoYcC ?e$u +zõ2B Ɵ7$L43X'Z h +侇H)eq'7l#' ii0 iO~_/TEE +5 +HqD01jx:w>ϖ^EzUlp1tzϥѴ=*FNeDw|* *W qV}S{h?at%pn^B9kTT@}OyA +)>EL@tƷ j.wI{̱a WQJD8B'GS'9;^/wKY|X)L񹨝a*OWͧ +8!,5 '@_w 7=W:k#2QJ +kF +akڐ9$n?bH1+,O /~kr_"_ȟ^wncj +Gv_ +\nVpvIP0aPbU Pė˻rz/ +[`߫I/a7 vC| |SQpy,; +w +&8sKԂqӷ0X>\l7SPz| Sp uI˂|ΓJU K ާ:5^T&@($pVyg; 逞? +1/T ye:dͩ{ P#F +zF{ +B Mv.P[vV teZB{vHFvXz~z|$@VAb"kS"zzQ8Zy 3'=s7K0̅Ov~p,I|e[t/r]Vd YhR̙btg]Q=W9^GQ"#^ڿWr[C +  ((182j4jԴoum}8ͽ}t]k}#QHe>␪%Dvt''"'!G[FS݉Ft'x~Lz2tR=iO(i+2 ժQnW-W +=Aczpw#<%'=Y?_-&Xxثw[Ŋק +'2עBX1+"  /^YO>?K3-5ǥqW]\ f1ly5B 2GO*:=z?34 8,h; +a&lj a+M[$EA*h|W݅~W&`? O~ 퉦8O)WTx+_cDV$¡b_kcp^KxO Vc2"fg +nd,m l[xkmcm~a>ɖ #B3s~IT+"j"w(F{_zPdR p8msPq)χ>t0o^"Ol?|%>`0K4?D4an`xak +Lw,}5ߝtɤ`Do9FH(/_gs4eWN{letOF_OI_xr.v<5>ՙY + +W$?/‚,@oLkS I>#AO|< bw S"{| +R^\k٧9PIlrJ?y,-gI|6> +/ +vֻ +G_AI,ץa/%sv .A#|!“źe˻7_Z#zu9 +T{ヅ vbib:hbmcT }@ b +jaS*:F-I(}C0ƴ\/Yj ˮYZ3JTn**4f_ Gzxm= >*eWHRLzfwjl +!y-%OD u ]ַ/Н .30!OK b9ʁߨ>K_,i2c/G',x<үj9?r;h/OR[Ǔzwg.A{S mT"@Y: +ǂ#׸)"2r7G:,M1ٲ?%}yI :'|`R?Q?tByv^72[|_<rԖDK5z;޻Nh +,j` ܘ9i[܋X\K1r7B\-Δpǜ;k9UQ-矫n[I?oH# !NW_qX3Aљ~nߗt^m8E7Z% +:S3  +_`eiůG狌_-\-昳/xX +< +/<(FTw(T dE l{ Wmēw +r!e=n8 +vyHAmSGhX6^ÆӾFl6.>QZ +#k!̻7<0i]i0Ɯe$ +f~)!&xS{(V^?MRX9 +-% /bd j-󷥖9A)vg  ~&wA&< 3q@Q(bsw=FA#I>$W}Ԓ[,ji @³W!y͵ Οoh%Thu#%w=G;Jզr$oyvSY^! })2mM AxX %3Bŀr3Q}Ht78?2$-C펲BTa?uT/^ k='A2>حȔl Xddo7"NRgze)<6[&x\ԦFXlmF(C))TɾXqPgf.IqY9K$?wUx=RR]qJΗS]v`tAK (c_Mۜ*icN̜! +5I!J?wDp' $'ME +?ҕ?CIY _1d1͆)v&x +g*{9=tL0{"BQ_}W%NEk}@Z{jB;6="՘ +Nٕ{@[fcnc% +0p_1X +UFZqɵ8\lH )7 %I\LwyG,# $ل\ ҅5B OVߔ +6I3^9{g* K4T'JtJ"rW[zed"5G," 0tA;֠7.W{v+i҄8ž* +z=>>\ V@tF +@JI&(L3g?&5G@࿔ "bGLFΓ<XX@FK+ +TNihWgK÷/8[,^했= `{.F ozs҉M6jТNV*H90dhCn>f}ZhO`Z9n T.kw5 ~5?_Z O-ߏ#ʇVphw?N%@DJ|U +ڃF +Ke`Os +9?H%68 .U> TWڑ"R^v^U<:Ŕg [Hp&Jfۡcu$>t[va +I#ō +w_B<G _|ai\B+ 5W +B@kr/Gd~GT)R4d>䓊A }~y*2sT)LMo& `wp3acht +gߟ@.471J-y_ ] +knJߛ՛ -p觡G(-z0vxܟx>[٠O+T_~,qurFYL x&gwZL 8l*u7ԟV֩ +T Q>~8| T'Yxq 㼄 0x̺g|6igi'&& Bb^{l/_ѐj)dEm +=Ef0h'mNDn3eW4#ف$T +_7ɰӠrf sV}_ _nGOe. +{" +ꆰf7mR YϲxqUQu(\deha;P/6 .v @ZܺpwR0R!8㿤T o6kBiuQuQOz2 +<3Q%/ +uH g9jZ>Â=z ςHwɄǤ4??vVrԄ3})Jq5;B/qÒرRט ֺj+qjI(X +-{whcԙ3|*YXQhH9&c#R {U#bJꙎ Omt%p;BRIԽ`pG3hiFp!n[M`If]'Y_5 wS), _tܷhL|,d: j%D JTB@ B$[@ q]St /{HCy@ >,8XRS8ϦPG[if)tct$-oQ-S@w|ī ^^+RńS`̅yzfz\!9r_ (Ɖ <=w̎^Z +whWgc{qױ 3#~+ft+CiK!PĦ +P rG쀪X%?zɚ %bTm$ m*Nn7pr@=y'N`f ,hs9+#Iw+_3αχ~s;DW#N`*a'pt\p2;q\azƣ>j +<ʩMY :U?/#d# +ED]9TaQ9M=jC"Pp&{&T"6%{eL'G ?;9 +Yd*eA'e5Gz1Q?_5c=1dӒY]px)Vpی'O ࠉ Ccx*CbpSΣR;MN +Ѻ~^@ԶuS.k+ .P?7h )8` +i+e'e%/lun7*^X k8L<3,/@k?L?ׄ0Ұ%nΔ_=Yаi;WuӇrבꁦ PfnuݘF2P8 +zVфx\@mޜcD(@A"!]b1A U@)#X<#:XQMܧڂ +y(RBu2A6OXs2L"WF4v7H^yrC&!N{ „_N)`%d'0$DɰX1|Lp2ɫTX )sDl!.ЁۨMQ;w`,4xܞ4>yܼ庾gkbfd{^`KpS4mqSWt{c;T + +hR4(|5@Py&W[BU@[3,(>foҐi|H7N<,EtL?w#^s_ӱ\u&`0]V :ՠÓw_m`ri /T"7 |iml;zǦ +z=#Y1 +PFtQ'7k3@ +zKJ<1J'LYC}(NMᎻ]pUmX +YQKh?Uy`0.N5;QO>RFhsVH zӸ1j"Har#bH^IUJs +,od'G!>RG4X0%RLːfT+ <8Cw|J6|٘k詠dzϣC~j&;`, +#ŊNy 0&:@w_JU?H): +b8,}*(CCL\⾝C oc0;PA`C!m_g0=/ i<ٮM^۽^<'S4`R(UzdI +IЩy>ttno Ī5moyS>m`%Sl*@5 &e (^d`Oޯ7`-&2#qJ"S%mO\֝&iؘ ƫQ(D&KVKE}ZFCCu^l4 5V +6 5Xln#1) +H] ؒNxuÓoya +hT ЉnV oVeY6֠tC=D%h)-$")Ho.Σ vt'WOX9 OT EB=/[x wyd)V+.䟓LIBs&T" 0$ȝk[:$!>f>far*r90#[y>D_z=_vNO5O +ڏw*__H +NYQB"⿐ct_!HOL y,"sYD ?< !GZiI˵/FsK1{ L!CƊQp*ͦ(!-MWΨ~_<РYN1I6Lqa & +V: l眛bT;K?8!GNALPy +UُB…p͂m.&2?=jfl1I0V ,07Ad2R<| +@E]pk)O%DYܫЁ^ %6HN{\z2g#>YcxHc Bvx>i0qo'-pY&Me'“L;g9E.kuُL &?R$qq3IH|y#61  ޣxV^[mNx?${~FW'7@5^bõ+wMX|Zיw=J_Gy{־D ޟI-Qo-i6D~Żdx~J*2^@97k?B1j~+슠]#P"!鎩PdazE"]G{|P?uu, _K_ OfY{cE! +Y QOEzGRO[9 +8nKD_a[54/f<n5ϝf&tCz 4pE_jOX?\kVXVb1ޡ7NG_%NG4f] +Z_d{… ,ڒs +RB(>˚PU=ˑ#Z6MEye[ܨv'9-|g /H ;q$Q > , Mgp7Qzrm5A[.ʤP aM:['?a jX"B+*N0vܬ[j# 1A(^ Qbqˑ^?{r$JwL9rPR/DWOc~uGx~^ioߚ|3pG;P_E~bs+so ޼5{ ncds[ R Iln(t8r<@o&o>UON#]g:f-i D7uP3jM2/O(c%).&8Gc4r*Z݆XJ_sbs +${P ܌\{{ +_7Kr`i@Ql͌iT?1 +6wE?~HAG J/#C\p7si?3ӕAf(@ܭCyC &lcD+;Xr +48兔Sf@,r䏻lF/Wn<(@ +)vH@P}$awrdP W$~|za'I:!w K=*NrX_{W^x>\C ?ⴘUΠ0[Q9ĿdV7@kCbgCp,"`;$HقO; +ZOzC\Dd ]2 q=LnK}#>jh?^㗦o&I2njɄ3lү +S+w_~R >C&xrHia ={/Vj;X僢ћ6D/_?D} PD[:ÆP@Z+kalMy3!˃!44W.ϗ׬'O; C;_(93V|,kPP-IVAN.P5>__39ztH!pPusgxIĄ ?צ.xkPdd!\߳'_a2k >'1Pdu[{}$veֺ`4DPpա +zȟA/+{Z kT0 +f#ƍW09Xo휂bq7!0ڌn`AY^ H[8C"C/<4' bCS;B4ա$)g ++EkQA0gaҁlTn_9>Sz.}e2noTc+%aQr(K3^9w,S{Q1$ `Kq&#{W/J\dzTȧ|ba-=J^Ocww  + ͂CF +  o|:]6#T}uH[L˚&Ph]jz\˼ ,MHroTQ~^4mmP=f:֡ YkZy, +z.?e̟+ Ln@H3]y-01+i>>Wr[ܮwٽ18"lqTtB>JCyX=P;FIxTJ3yzSRZEYrdfW9&ܮ'92|x"uYcיĕ+m{]@rLd cYژ~N^go6ɍy +Yʮ ノؘnnO~Qɡ1:.b_e٠I~l~ȟ@)TlB ) +j &$G i0DW]}~ ,Rk>遼'DAVooGeE wy|LzS?&.,1a߷GDQq,{]qpΓRd35oRCk3>VhA£,7_mD=M&S#۬O&2؇`ŰR}A?4{9tsFM@>U// 'ǎ6B#Nz?UOczU;l~ +濋z1Tfj~Y^=>>B .aL3Pۏ_򘰾fEfY#, +Ny9qO +࢛{֠K~:;},{U_?R۾|jlG`y U{rz^պR'&bM7dth'F@,jo+8"L;1eyN?eOZ(1j{e|\J|h{e1Q:Yn +5#p)PgC8T5Z +o)FFM \|/8>Հh!$[{.0Xlఽ/8{r*;4``a&Om-2.g>NS ^:?u7H WJ +܂_⯜>鷏?!4x o + +}ĭj8;gc,Ն#) dn5ONjDyO./vP%]3\!!~`eƅ@oSS$^jnQA}!Wd%@t/ f&uD +йdiE֗/ɽ-ɧE+#I^-ΠlsX'EGj*,qQOӞbp + `u ω#-V5*Tbn4gwN$]lb~Mu7ZnI7oW{/WԔu/tʂ1s1Uw}g GɛSi%YQoF8lC),A2.M +"$ + x._OhD@FHS//r;l&ݶ7+7O{ L`pn]ౌx_ +hwt%$sZs[.@^P P#oq%,:Ҳ9; fy2?i\TpVE_1ǪO7Y*/+!¡dvY]5ܟsQ +SO*/C Z1~<>0CcIm"dzy.O +t5fJ7*P򮪴mI]}aU a:>gW4hWv=|hCX I8!mlȥM%'y=ЖU6jݦD%"vv6}0JruNU-& Uۄr||\kR;A=7صk:u9Z dnQ⏡| +͗{UQ[Hrr7oG=\ gwG|S <8'M& !%fPh +%(@X@[ Kg;D~(*8te V:oVq_&̓Epo'[lHߌt\w$m瞿ݝNq*ɮ^261Y E+2(Vמ~*,`1';j]0 vUs|ĞTg? +O%?v{HW6nlà)檺"&cD0egfmm[] [- +ƌk }MAʧ,$j +vOajҴ}4s0byҥKNb0w33|wqsa1xjRrՃ^d:,=nJ<8[1~K߱?p?jxo<_cM?BLXl?ˑ[[U6cf{s yQ( +A,`gG IKJ@F0&瀀+?.hh;20m?B Rg%QBzGr6*[4P?+k}%4wG |1LCLxl!i./(N#2鮀ULQ7{$$y(Uj~'MB +!34Fx'_ +D,D7vq%JқUzA>EG+Fd ~ ~I-@. To|2g+ya{go;P]?6䴣ow,:Wq1#P?y!u>ب`O c +0AYj_lr+N[`g>_ݟI +L ouvA( +B Ϯ_ $3!Dto[L #dC Hg߇\Y,}"vLjY=oEK`GB̎b'?,s +pQ@¾n5!*b]!__+: VBBI{).Z8|࿙t)B0U!qr+;5o.F`!Z07m(?J{U~TDZ?)'Sv}u..)(e0.@RLuO.m'!]g;lJۖ_ŲT%F + PAJ?$'2[C (@<>$͹p54mA'tP&. +}lT9,,NRί#68!r )ɦ4wӳ)Vc4y5bݴ5id)#[?0V_lX@UUofsJ\^ +.zI׳c GdfY'pIiS# #Ž:JV,מ$ó#PbyXe=PVa[iwcq)0No#;m`rA5x[eeA?rLlR! ^ IИCt-AcR/6>9|;x#BޗO{5OP"صr{{e@ ð +Wh~-n^fbhcd +t]|ݗOc}4okkfřؚ^/8ۧ ߯/S?Iʇ+V^kj%B +){'inz0 +1'PwZ|^لS身k\JgM`?# + -wfFh^8D^XqQ„GժnF= &9XC *Le7OSԅ +rsn0?昼^̓d}T6H΅=s5jzvP$mdI f'G) XUiH>h 2>f1T7o)~9*gni +<Ƞl|0R1F_Ϸ}4c8/7{郀CÓ+zPą[KP%?0TD/O9cx߆>Q$x+;/^EC"''WLݦ꓄;W31q`|Ҟ134>4(cwnř +,~s0hމSjԈJ  +wg};VeP;`+ +]^qR`1Q&JAk%,},0 +5Zidۡ='>q=. + ;<=ޘ 𿡋"t8Vwp9ߚpт.pR [ OfSΐ eJI^Os]#jt́``WXVE.]#eV?%|-Wn/濪WHTOTAԵu{9|X˲%.Ai(8 }*Mylޓ<>G薃A=|><a], }%;h)+, + >p4d?3I U%ׅ5c}_;>喉}_gTSPT_p :6O_O|1n =\ Ј4Z` 7̨rj,`$5bh5xX_R +ݘȰ+PsT%-ĸhˉHB +dߩd? +%Џ1|n;ێЊčp._V@c?.'gZp{&x//x?%1c|fdMT\gDRχ^mj:$`D +926_g q='߭?i޾pE0 +ˑ,ɗ38B,DX#q) +xҤ ξ1?FCFyG/}LBt uT{t +}e!L]PT9@L lW(JS +Zo,gyeA4_ƌ`ڤ n q?coKy3?OnWM@p#q[z-M O27vl_Sj0=m :-e0Пh6&_7]a x7L*g ,Q=CJ\t[VV"Qgd +ƘpoNm8\vk:? M@s,MrZfĴhHKeɖ'Oկ|ޔۤ\ ?\2.j0Dτ$LMM>,`f ɮ_B$`??hza~l$%QJ0Zmy/>+ba< +p̶ZMqDuV:#~1@Dz򗚐8N8L媖fr#6f@ho7O̹NS yJB3A`]U-[^ T + ֥Ѓ<?/(?| B +, dRY^j͎ ZQ`UH`@ttt@ `0"Zȵ'/Oɜ$'$Y +*[U~n ^m?57Pf?|KG,q+5kZ⢠>%5K9 2}{`Y]"7 +X?l23"ƅmEo}x$Eɳ{6@!K& +, +Ft.%~ח|\@q[܇4[0p,-qZvXpLa(`@MXî ԭLe +2 ܽ_#p5]gJ/,?35d3yNqi?78HѺsU+m-MZy?'6_]ugqH!t|R䮶nv`9Lk G|Afn B2U_r + +){zmDܖ_ +ɠ4ihC[ +p +@wņօ$sY`Y"C*}BvɁۆ#{*7oCAR77Ο.A@f7l_r~ƶzaXR +JόDgd#M.'U +DZ 37"C +-P[ u&5X|7Ǚ_G +@r$7058 3Jcf{ʇ>R_mB`u{gEsuHwxeyF>&"ku7G @x=^?W]k +%YÕ" +Ӡ% +RuM-P:t>{9cۂ_߅g&b$/t7@g +!WBӍ`sIY +~;m\0+0ls\O"C5!pls-b͹H\"6%N[d!ܮ4a~ Y2 ++!r݇  +RbH-P +k[IL N&' +w^*Cz7VۧݛSz4y" pwvۖsNl(qu ]?DבκXu]o^ݭ{mE'Z{ك-A]٪ +e[SHI~Pz~7^7O, p^DP` ~W}Zy9H\5S akMrI;&1S z*)=v%@i_IH^  +lhO +6թCm1! +I,L5'Bn?b^ {/Q E낀翾C?VXvRشPW0-% +3e93Bp$.ݳD/לuatN|j8?Ƭ8nzm|1\1r_Yit,7&RgN״ +Pp|8,U`0U e̙Q9t*$4疦(?1_?A/2#ʐs8/K' wɕ.rBÙwk~Po|` ?9ר>"xc4p_\ h~CX6q9 ` '`@sGL +[\2AheV%!26D9@{_ +//T 뼿:Zꂲf$Q_\GcSz2@^`Rā)WNs*~&؂h4we.T , `fdO6?ubslawgsQAIz~xUEo9ބcu +V?kfV 0ґp8: ƄW r"R ~‚:FywSǑD/P"X'mG_\trB +Bm;424=d.8,Ma9@΢KӒЊh] Uj=@aLL묎X'8<žgot@kG +ayϏZ PD@}.3g!&Ot}oBgn6&+8aTc5l1Hp'BLQbhۭ{}a:e| U#;?ΒO$^BְDֈ]ɻGP/GQ@x-R7 4Kh3-$U6hB9=^|usQԈ&(tyw +%r˩1ol_[`*p&z-69CM.|A{ri-7fT#* xt +l;˭<p#& +((@-GFVBhmA۞&&ϳ-3۱bzvIEIMuċ%Q& i4Wĭ!AV( m + +w zDǵxIz5?: +oH p +mV vZ%kcⰮȳ- _Jωzac,`W +Z[9srp>3#,7OABk!`٫.z _݄Nr^r]Wd{`rvUr.dߘe;䯞PU)hGp1d!?J +caJQq'ݯDojŇ + +# JxB@p)!I?,FGc)" + + CG,+ h~}$]틘ibm܄& ]1aF[Y<;CdZѢj&Łkj#  + +'Sr~h0WSPJKbOJ pi2 .@23Ҳ{9CeH*ʊgiIg) f<ƶHOx{ fuʀz ZyJBa \co$EscPs37y~fcI 'fxTdVyvQJ@CuS[i;M<7G&VZU !([vCLj5!g~wpl( +eҳ4DX2S*WWk[[+ ;HOƞ=Dž@PS] 9PCn~kgH,A3mQCtqZ8 +`(1F +D|Ƶ/YaO}[$uU6eMK 9`j>'0fcm2=QMv:WVX*KoDwQ: +XOQu|^OcUx,427@ heOr\ {5hYU<&'խ~nPe[3!QdeXI5܍eŤv0hSi)Q֝b0!M+̤M-7= a с XVl5;@}1o8LV~@ +,7zApL߀4@Kip5aO2nIb.F^PS*L  +xT}Rkb: zs)_HJkIњ"W;:+;B~g.E%>]]w&GH䓆tZP ?.Xm2efvCbB Y]X7|ik,S[7+?>BsP[( 2$ ɕVHڬL;=tQgaJJZ;0@X$R0خ -.`|by2x2{%Z ;cd|0:69DKibh/Vrs+ff%pb(u5?}PgpLxH`#lȿeOici퉉QGG+Å39'ogZS2L%V2%MY3'x>`wfMYDڔ7Vi@g[#x]g+?}:8@^A\ h_k.:2&Cb]dF@iIDb!(`PP4C |?e]7DPBp J]jt*@f+k,R P +ԋbO1q=mCS' 7():pg@M;z(gox | ^s(.xܛR<G%O> *uq@Y(%y!@Pva@_9_chCY38HSs?oQ.5MCtw> o'wGp-'O)&ve)xWUɨp(`ԌyEhMx\!("vq_A;unFBWss%s;R$P*IT*~"7USCuZsXIj73ސF@?WE6G`u{1D1*rD˟ +bs?yGuqhKƛNj+` }Th);`R(v!n> {/KmUpȍ `hs-jɳ0% :Gǂs0T>qoA2@.u/rykw<{Xu/s Y*;^[KR0 +Zv7y N3)h =N^Ǩ߮/J55>/@ +Ō_kI'?'Ya3ad= xOK^#2X&8p>gH6~i,!@{Kբhy5S :~fp 9q6ſ 0վQ>DcLkCd4Ai +9{9S\,. vИ-zp,/{-[/γ@AP`[z%yջ +,1J>&cԬRNH1B^0JhŕDŽ>cŵ61Z8?-32O6{&i+4ơ +1bj 6 ;X} d \w4~:*_!# +.`u;u~/9.8=Vx/snH= N9!0rp@:Tr,Pk2 }vnpcp`^ +՘Phs[xJ)0I;bK/|F{p "^&мV +DpԦpLbHnr%ShōIf7~e)(r +ȳ-<yψq^mH@%fYEv𿢯7XeyrDuh>fV)K:ﮂ6/0 +qfQsOPT-l?a(eo ?<*S"3a)UXxbޱ+y/Džkkװ|yA珽4Oקi[0o`ކR@Wx NxzV往_v++;Y^=ل 1>ORX p>_e +!FjŁvx8L"& w}T)ZrEq?У0 +)4Uj $οFKC'>?H>CQwM䏞Zsj( k[}~Myg>`İʟAgҔ.*_ S}W$x`G٤`Pq+<'U賏IMls +.pz +y?. +n5 5#-ߜ'p;:_>tz륆 %@q"g!_K%ILM*gRӕyxK09$R9I^8UJ0&{?QæJ8㊩ 2w}ڳ'.E᧋/>sGdX*EK +m0_5apCP/g@3 +JJ'y! e +a=I =϶s"vd) +صCQ}u(\6w +P$G# +.@qR}_fS,S B+ǻ6IlT@Cьf;EVPTPJn8{: ?I!DS6z #ln wO[Mîw'g ᷅ ++vjEm Q#pp@H.z-AlGQ#u +";3W|j/_y'"ВD̞ܙ#H0UڭBQf>]X(?7vb<3 xv;bρصgiE){*.]g7+܈s|@D*75T$,+^-)&2Ʌkz8+J?YԜlEdƭ7Fn׍vƺɘ<7f+u Ԗ(> {qhtksߐ ޺.J}ѡ +| +9O!e ڬ@kܲwa<gxGPn`m 3Po 9``˃6f(^nskvoj:G += +`xb$bqp{&u(E3;X6\3<1CXF<FMX paO0٩WnK%zc㣭 +i{lHS. čY(+!?AtAA@> !݌T>TNBs4fUÌ +82k?Q$hsq$)G +moo:ڡ9dhkV1`Nea + +QͽAσϽ]&S5X}zr.ևaNj2->!)@DP$y38Jg4mT@8t^hMdpEA.(یvh߻Z'0$fpT* Tヌ!5p2jlzgNq"zeΊ,-8$Sl8v66`3X[RL7i{WfɈ +@T@E*k[a?8j?9[0_r |g0˞xMӥߔ@} +oOT;{WUiWQOS\}?)` +Z{o,worjCװ +}u+Iz)fT1>l\ H=~KS66 +gD@58X#ES3z + +`@, =0 +8m#M"3Ǒ/pW:b[U؋*sO7pe^|C p٘$Cޏ>jR_Ҏsii~Pj0@5 Lp=0A +j/pzT#}L?oP&z~)yM(xF +MhefC! ~#ιſĩ`X +/B݃rA4PwM/1H/*/" -W~ǤF { +i"\OjӫcW771I#LT$>:qHs] +v>+t]@qf>~ޥKti.AxE<XH;hlN4L_Cӟ7#V/1u=4$XBȳkr^zlI.ziy=oᬳ+r_{n׵%N{׶?6]洫ڞ5҅3XA2@/ +T +Gb}od4t_'^Лm}H0;ؒ =75j6TҊnlO؞p8akڝ~YjF?1 +z}eGGO@Yo~td䟂 +2Q9i@Q *0B +?5PyY+?ښ DVij"h/ >؇& |~Sh]kؕ + h_o'=\M8<3eq10gjnN +:cc?˭l +[h\4{8nyyh +_@% +PT!sG/s5)pZ38 +Iս2\t7>\VυP.cFD-`튠,,Vy7Rf!Ș3xJpf-.ت3٘V)-у'QnnaB')8;= + s7dБcoD &(3OGiQ`1#'H1*mpi@ZFqH:#:dIz0gM \œA6t6SaaA.%% +k\]ƮWox7|\ǕKDY?"ǘNӥzo?~P$?ED*1gVx%+|>\ q~G|hZa&)yf +IfHTFޘp'X_P;ch{O3w]5 w^ ?kӟ.:Dx1R7 -Q?HlM4F +Xx\3h r +r^20?!a@.*ނ1(s0bM %@cd!z49N} :`@ +8:wsņ5 X \L&L,T!0:p6s4a]$d5rFСzB}olQl((DS'+,l#G ^}^)4KI2!F-Co?Y|MgCi RF2$̠ٲb 1vbJ +Y`Yr# 4T^ +zK"|Cs2@x+)Ⱥ`K dŴOoI[ +a>*jhg!gvTV)@E-cZ43eL#f¼ a'-n6Hy:Ch?"_4K3<1K|Fq *A + V9]6[(8T)ޠ`ѱnq^tVJ]j:~P+J& )^ePT +fǦkRp* mz&HTܿL`hBۛu(G` |2-X +873}>?p{75?pE ©0?phw}#M Ҏfz=sٌ +`Хlwq E408? OH=0>n]{0Wo;O>x +Q p]F} H@Լ.\JaH2{ +>ЀCVHlUI)w ?I +R^3gK:rf[$ɅN䎃L3} oV{8VjF3 lLEG +S +eHxX>5rNv5h7I08#Ld4ߘ%HqWѣNĜxuSղ$/id̟Du9ѽ޸hBA +xhR{q&hޭH1H D3IW/3REXܭ/BDO :SB= +U2BԕJ|%:َ?UD٪%G}Z$=&R$>տ|'GQgl5[a/{/i䙾 +?kfԥZFSBgښ?@oK]J +П J  +bQFW`,e +[bNX@7Xs(P,`g (6i ^.ޅ,6SBu6zJ +}TW<XJ +@#:~p޷ Bg6sJ2OC+9RP.X"V@/vr}:eR.UD^a f| +5/D7/%1(`A7KhQ`@Ak4 + %:XFT> J'TlЪK +fDUs)]~+!̊} YGP_6ä_ +XE_Z FSW T\&R +coui8N,X욚 ]5{S_% ǩV|"d͏ĺL^0:n:&8$5UL\X5dS +ɇvM q%4 z))8?ڍSU)43}o8HG +a3a +8/HF?ү`\Q;&.<)@: + J|T &KVU"  v?%h;>,Nᷗk%}k +DrcY cO8ި7~䍉9jĥE8$ƅ1fG){"խ( +LtpєbF +%[^VRFӁ,f2#, v=YP.9>-=S BF_J4L_+'_x7EǏhbMNBBz]h7 _pw)}ZN {{`Ǻ+|T +nlv +z>îlpeJjihtS(s1z K}%;R fwӰ +I +%Z!5F +c))rM]eq  +3ӨʊPp 0وĔTL&1OQ;()tD z'|?߭-!"45D/.`9L +$Ctl|*`R 7#Υ'MG3Ꜥ:)+'/r[:|OpzmSEf +a>mgH|[#PadzƼS9J@v\SG7>8 U| +}wvkl*O.oQu6_łnoeP_Hngf!cwc?j(s~.7᫓-d#!.2˿"ov jcpl8N,vQ`=Ay¡ "-^SkͺsV!p0'q̓>Ug@k +w QdK !oh +;N3 +".i-%PToʵ%̠0rzkLjr(?ٮ6 .L(Bf^N(by,3Q.SZ +tQ:, +>l{B +12@ #d1N951zIȟ{,b+ub~$*aj"yX +<@y˥Pg WN0!Ȋ\T{b-qmSec( J x +#CXf8oMcH +EĴ@׈O쿌Y F|DVَ0;z]p>AqNUMhGG㥪 a['?PhW GʭwEW9\ï +NA0x , +S  ?Bc(P@`xP٘Tn:Zͬ_|Z {?M[r}J)oP4i%:: +Rm +E8Æp7uԶ. Wrq`㣆-w=JTJ?nU7# aC3gu'D(} +L{^%Wv9 v3̅&xVȆ86u3'O`%z*ʫ}BVbTj +0@B?Z' vU'4a1Lp\}JHw-\Bsٿ>=5ʧ.FԌ~\>UHR0>c\L?r%1KSX>JW^)7jb^cf0);| j + +?u//S-@ߑ?> vфJR?p2 _m?WK0>& & +prlo~8+SM "mV6v ֭bJkA":/ⳃʩIIs GtQi^kgO0k| ;FWt^q xy2<8?)rf+ t^C8ԲGx+p_]?: +$\s2H 5>'a 2L@g*?,VֲmH@NJ$$%F"DPucsFڙF}!AeHku 7 +nSycpk@d dg`䊠H Ye? Ń@ +ۇUȬ*.i?DHU, + +paD0"@ +4DU5{KIoE4o @o~5p14EJpk UЪG +,}45zLl;&YZSe[縜Sњ +/zUN @0ƫ󖏾'| lU^ָ3k$_Smk]-=nFKg@sNz< ByRõOϏ@Hv χ6 •c5 +e0%` +; AA=;yc&,$̙ @%`db fA`n;:bVdK{#4Cx;z8(K̚Z<4,gSDQ#7K;3؉Nc:f +DM l5V't6M͊ۘ|u +@G*@C:-~J]6FaQfwn| |Yr`,<`*{}E8DsB¡@I[C5 ++X+`J amPt/a)&$ +3VuHVܧ9hV^YiW/Ú}rZjŐ~M\ghaOe0$aIMU?U£ *7釚ҀEOߟ9`^S MW2X7hǚ_Z,{;Sפdᩱ2T+XYg*^0i SC; +;0Nl.s/y! z?_xdzEeOpǂQ F?8tiv;}zug_}=RŊGUpaxQ9:ϟ\vk +_=t'7ߵ}ϕsᄊkҗvlxOeΈYw{Λv{ǼOuNVS|ӹ:h4FT`VЯL" +T"6> +J 1ፍ?GѠ&Ip3s;j- >ZSDo7f] b l^=t$KZދ}"CP +x=- +5QMeSu4,J<ϛ|?;l75 m e@%1]`1}o>??.΂el +FiqL%D!7PFp.ߎdiTh><,U >U.wHUa>aBS ~X< +O$e?A>)~@~5'+ꂽ&\ +aI\ ~E|Z톖;UD- ++ry"  ˧ߓՍ4L4>u14!eꞬ}!Coyq۸mJ\KB$_9үm+0%,[L117{;ed!MQL}-w)"rviHš< C+ J@\e;4|4O#U8?pT-}ڭfo>Ӂ|ާg6?tYNqC{[vcthߴaÚg~bJGV`?u88BJ[UO>SO9jgvjN=SjVڠf͛\t9}{ܔ1b9y7,{aڗJVe S,sF,cͯz~'0*auzXa{&SY0_ɛ ٠J 7T6al&,i#S +(?OǼVfI,ނ,95w{r K\&k?s5җ@g`B,vp , + MfZ%paA^&gbm_x5x4O#U8i. DC~aMvn˭-cz\shaS+hHIhMs<^s*@"qT[йWĪ"Wf+/Zevxp +j 5{QkBT@#ޤ|[g*?R{5vsya> _䩥+-|dދ[jp{o.* +kBzM4zIhH #%P@jJQT,H齗y朝& :;L 9OԀ3F :"6tbڒYRl_×ҷL;kG\d[av쀔 Bj<=VW@*#3"/)6TBk BnH>̑e,|. +ށ:tls C$Z +@ {C:8g4 j?~*aә30xg1w$ +t"_1{y?ݐ$ԬM5jQրXMak,u\@MH;ӞϲPY3\8O`6#$/'u{ F+ۥR=T2*&L +@x"b9ZY@ȥϡ| tZǢN;vl~ѠOAg& +H + +,-R/Vғ ǒ `x)hTei0`H /.6`x.=E0繓_bxQ]DxA?>5c +pG,ɏ}B6 +ϫ +8{o@yе(w#Kc."y~!8MC8a. +.B{,}5N(@0hF~ 3 +@[ oCY!ZAš,NCP%/Lv#3྆>©>A_zAʮYa3 :e1PCǢwUd +I7.mLmK kw:)e)HW^Vbg +hówi ԉ6{nƲ"mkS +eO +Wsp&y-XO,x^*_6 dii>? 7>xXX +5$&A6l1* б9֙#!9v!Uݡ&m!Y' +LoF"Xh|`<[_s::6X"@2@d1T +AvaeQU:fţY>cRf|Es a Ѓ \)( ";C=nL*i[ +"{InFl RP%tkAx5`<0<ƾ; Kն"| KEJ!̌#zV#W**+Ž+A0!6/ f ~^V6GBn@:t~-"]sKR pg}t82~i/H`f9|!/XzĻI+J@S-(ZvA +HQ:4a9%|u`a@L|4 LrAUYX|mHH $?'B5BY~eui4/F>SHG~15>lR|}1bǤ /\0ףJ{/<ΔQ.c)XhbLHVv,!X/t؇9=.Cb{ mo9݁ILeb/z%gE +Oh= & +H~%\ns? + +Ͽe; +ɑzaEevm""q_\Vi`o->D<8"ԭTH1%8H~yo0iO?2+aF@!Q/jINo]@nMyG&Mrat@ӓ<w[k$+aH` +3@Ȑ oӆB%XRzm!"QW +XC5©V^}OHswgj2ktfL +7obP JEܸwMހLJWڗc?‹p:N%~_K meQ=&Q;>c)/#(Z'KmsN<֫% 'dɿ@0-sC~ +[xs@"X5%}Cu,3 eT4>4r:sϱa֜Mʂ)'6e>36;ny\+F$:% +,- V +, +Mֲg1e4%T{+ +/<^"eAdK+6'+J_3Vrɡ  ϫ߷bEN'j(6.{GvRjm\8v +YxJXے'Ff |6 +hzԌqx<5# ?T!$ON ZHuhSLڃ Y@t"\֢R='y4 +,]VZM<ct4t%Wb TT#>d LJTDXq:|+!'a#e&'4n_М J-I;9L +_cOQV@Z;q!T{KނECTS溧xO&,C A`Ae܁܊Aq$E!÷0=x 0#!>AO OyG +C"W@JD)x/ +\-q\pPӧI \p\Q8 +՞-6\p\Q8 +՞{ .BQxŋ'O? .BQxݻ .BQxӧ;>-q\p:?. .BQ@\p\Q8 + . ?' +G\p\pAGD( . .H(BP(RBGQxuB +=`bw߁W"vso?lr-wnۻld?}x!}?zc+Е{SiاXH}Xa1}}@_A0>x_#>yz}BWvW>zL߲}TI@ߴ[~V?1odLOF6F&^ix[}d>y"OI}) {o~FC^]( +BP/G!k P/_CP( +zix?q +BP( +BP( +BP( +BP( +BP( +BP( +BP( +BP( +BP( +BP( +BP( +BP( +BP( +BP( +BP( +BP( +BP( +B^C]rN}w>}<$=ϟ|| +BP(T|\RR_>w`Fʖa5+Te]xۯ67 +BP(껯?AZoќ?=rvtĈșdU~Q( +BPUc׭ ]OiǮ'G~ط Q( +BPUVFo-^2,sa=}{#|( +BP*onFZ{!׭a׹UhΟf?#?Q( +BPUc7!FS \tʕ ׯ_yڝ;7Ueױh?~ +у۷_BP(TS };V齨̏f̵Ƌn˃zßӢ>Sa=xpW#P( +'˵ l|~7Fؾ<'"ou5,Ν_ݾ( +B^<:HVgǮQ1gdlFyTq*{(c͘F~a@s_X4WV֭ۧ]\@џ"P( +z%+jdnoC^ivvVe?|Fپ?J3ܨc +BPWu[_Q:i|.Kn>X4(;>]rJɠ%.F;޽[n^rP( +z5oWP?uqwFi4V +BP*+Uv_7 +BPU7!F*E7ܼz*7󏆅MKr,}ݹs Ο@GP( +B*E W;^z( +BPUJw}E׭LynR].S(zaGP( +zmEs:w={GJ"?:sЉc?~g#Ǐ}( +BP*+(nK~0oBl-G5#:ӗ~36fdTΙӇBP( +Ue ta:+yu^٣ݿBP( +B^-ݺ[g<|p;}H'OdGEwnƫBP( +BP( +BP(TW)g:?L +9Ί0"s9Lw!+ +E2u&γġPuD>(]|;i%>!>>a|{ys89%o)vtvVs-Nk8j +BP( ׿CjE1_'πDdlO_@2 d㰂aVP %+DyÆ +;>Q?v-򩀜^m/@1+)0 ;kzi=z֩SZN;u/OQS jXb צB]CP( +BXrGTo]]"BA5([) yzQ _%0JL2>=q_ƳqD˿[g9SuYKDZdWK>^1˾Xlײe_,ytŻ/|1ҝK>DKg11#\ "gQ> tDsf9k3gn5sBf|B4}S'|_螮>)E+hNOF9 +?عn42+yYd%teEEF$QEG #܄B:f|:hiS?h}} TO~1)V(GWk;6cp@q@q@_7A?5vB}0j6"W'ϾwhI{=+n]]* tKRͺհĞ7iyi |f-78ryfݴBuo|NWSQ.O싘úG1)} #+1/tG蜭f2㐐g [6ϘeFUȌ3SMjLW}ږiӶLeO4q 7ax=@jƱ#~ذu + -J^еC᪁ՀAA+곢O9=fuѹczT?$oU=)k_ țf#vMy< +iCׅ7rUp@q@Vڛvo+SIy [t~JQU57Zn + _.eu\ުzSRZI׵!}_iu]KNERY$s1Wp;MS'n4qI'L8q ֏ncvcF5bՈF$Ad}U#Wzx`U ^<|k[KVגDuP!5CVZ9` +J^$#W#cT5߫>xuBPPЪ +aZNiZoR$׿ͻ6y꯸U762%QIY'&A:q^6kWߎ"i񕷣.ՎT{Ek/ 柣Z<} ?-`?yqu7p̨G>|Æ:=)$xeuE z|3h`΀3Ϣrf׏'Oܾr{+WDd7{۫w>қC6R^Rno@nπ=swݹKv.9u۹SnN9:u흡֤U*4$/DOweRpqrvZ`m1lV6Swmy<&5KVgU*U3=-55EtW(˜:-\dc9bYYnfj&L 7 IBp@v E2.՜;R!?whUr|G8o信ՉMl_R;eܹOvt#_KzUL +I߫؃UB +}? +UR7kJ*rVi~2|RV +7iy#K.)) +1}<\>P_ +3YH9?2$9[MPR .E/qYxw̦U q| k٬iCeP?~4{zzB5UlZHv K3M&lcx [-l`bD$~`SS{|gg':$rEIYO_5ͺwSj]aɒS&7My,sˮS) +eRTf(ݢR\OLX[MԽnBFH.Mu-TH;Mfd3Mtc;–ңuG31QX;8Bwv^MYpC@U"&{k+9CE!!.ΊN +SB7&-%~gۢۉ3mn"dZ'֒De3uvmۃ)Ùs |7\KgOΫ+Z+"fU~~:y&wurw$rw?w@XIi%c,]Woڟv3Lj4"pU#FϷ lhP(3,"[:{r); &L~dSQ4hWc5 ''6pKQ=GR;z;C6n^p W5zFJU#M;)љR2o*x-NK\>p_qtphOsi78kx7| w#abW5Ģvڦ~0OޭZcHK}O +;KiLߡᩩɩJ7 /pϒ%' NɱrH1i \cܘ׊˄%}}^mK`?vM_8T73jãN7CՇ#ts8}ŸbME_o,F$ޘH"RJS+IDvIvvv=<(̒u{JBK|hrҩW|-+Z&g^O=vpg1Ί|@XTT-i=iq/VBY}\5y'1$͘f(M29w|gRk9I[#zOo!g]Xi>Qv~}R^+ G +Ȯ֨5il郣MJ:OOU'7ӬF>B+u KPs6-퉇ǟ5IO[~.a3S#3%)юmmc{+KBK(Ydu߻Vn;حW\n&%GMJ +*re{MĺM#Jȟ 5jRw`䖚8{MK?bwVrfLn=ݎԀ1cDz9}}x'WzZL +5| ?/LV ޳i%)׊<+? :vjO >aΓZf~϶yc4>ӱ[aCGW;epn]*g)xEe +ʻkyJw=Os򿑆ވEWj-,Lk#XK"I`lcCbcjtp%]\RYI8_VG'q>n&%Ǩ og=!APV(*] +\mnvϻu'T3ZBZ<-y:]3f&tXWM6&|f:EK}N ;/å'a#9QɗjV> YVTy{zze+]]q1ZkA#2#Βg{D5 _'_a}\4[a1*5>piӿm0<5 >72@@>>fkN5| +k62 +oRfo{P&c U4nA66 zW˜#oniVjO6Զ1'ooOxvcA2J~n%j©M;w7T;9 [c|Q5cP7NyqMjs3VҝZ)w +_.E5J3yVDٽ{'& !2rB6k!wMݵqk_9!Ia\_'hG?_:[f"& +X"UH)+@Ԋ,^.P}])U|@2 e= Qla_൭_&dXլXSo [V}u?vhq=o̿xJ_/SUEodݣYJ%Y('AAk5c`ô;ZX}||ħ@m]j3ݒ +-iF +L4 kL[ [ ;L,;l{Y +6f#,YX kMd;~"'Eyb= H M +lGT;T{U:YMH]{'yuIW-_Zف]t,$!˫C~QtjoJzFUyf{zd(h3([VsڷڦX4nyS>Ϯꄋ88*N.UJ {nmU?ޡ'u͂EL"l,6; „2e(ClE95,-S,RhjuDml\;;&|{GgN+X# +}|P_ ]^ۧ;G䩩r5\:[LHpOqwMruItQ)b. 2mn:ʹ帶?圫4 +^sLyw@ofqe־5L2m +? 8Jg%.IJI%VތAn:ͥ׈{UݦݜvMM#MMF-5k۞j9y57_nN^-b-,,--,|4/.66>>!1)999[ȡR(J<Fx~r9ŠI#vfmNxIk^@Jd&jZ'VW׭ +pځjk_(Xvʻ.2pcC_ܗmA n\U;1+΢<+,Q]tBj7y`QtYi]̀!V &NXj-.7@3LFTǦjm8:]rPwd1l@ݐbД ZSm1bVKj~ߊ +,8FPUACj pCA&} +aiJFODb)H3T! q((H0dN +endobj +170 0 obj << +/Type /XObject +/Subtype /Image +/Width 1024 +/Height 768 +/BitsPerComponent 8 +/ColorSpace /DeviceRGB +/Length 414262 +/Filter /FlateDecode +>> +stream +xXGO_1nbEJQDRA,tTzQHːrpd=gv7SDv0r1 ްXΆ` Q`0 pT#&Gtxsp$X6E?Y\ + +>ߞdIB9R`0 q +nɫ ۠.З&]::;%-{3g2t~m&5JŶ+? +uz2ڒUU* VWIl*^sOL~' WV/^/w$~ +2h(u `0?0× wt<;wYu]8pOKS̳mr2*n^dOٕZ`/uLi=/ZsVF +@_Y 2BYGyed?c`&ձHux7a3rݡOA`z&?#9,\ZF֪+?SqpqEYY`?)rWشiN() | ?EiӘEIhmվ +Ik?״l-5rPi/aM1R%7ru6 .BTHR0ogmaky5_X5 `0銶::^beoSoP-SXtَ_diʶ-F>z&0, Ah0Rl0=]/G? ۪o +A|^ +^f\( LпX;| &FtAbPض^- +vC{\|w!{9$ʪ',7r6qn1k +[,LwB¼<[޺JvΡ`0[i2o5agզTo:G[{BSei i?H~-mIj& 9L&:=b؏d(UbDV^JwP} ~LVQ9Ȼ')aѓ K>#3 p56ڽD<}& ָւ +3﬋F-N̦D&lMc!\#j,\Qb$(#t1;br5Q2lPvSW3W5J3sº 5Cא{XCkyOBJ?`0 fŒ!nN 9j8p[<} +m2Ŏ_NҾ}'E'MQGw?ɰ(H ^8Q)NQ<@.^yl+bw?^|;{ށ,B9I`I䏾#يnI &yŔ- (dD~Ai"֟tic "s63'v6w1OH!ix@²tP=L!'A1,_yBlC[вDlKCyH5(!iU+E3Eєn \,K ]eQhf -/Y'~ Q'(d XVx{y*j_LyR6ue6`06ูFC؈.b1!.ݯG߭Mv}}\qg;}L!̈́L 1L&*^YVPY3S~W-0Z`v9;//S6V#O}(.*Q4&uV+:҆n1]ʹ>r=ߛg<Ô݅taYp<;|O:{ȷsEbV)˘3Q֙DӁQO8΍>tKZYOsG˭];[SPǩ"& ia涗,9R̋,[?wݾhz0zyۑv3v4dkr`0 3hB t]v-:&}_T2aTTϔ)F=r`01gBQ0L& +_oM[:4 +@6:s]GV?f^vإEZeUG)Nn1icٲᲆ1 7牷=۱][>ߪ1c4sE$1^ّT}eː8tC30!#9Ҳi<|i ,,Ni@36|FBz=4P%P:Ik|r*4)1E>Cg)7fF)Я]KD"k;04}Z>B,B};k s"#e2tQ0+ysCM!vwi 3$`x7?j'ޱTg:֪֪WJ[F[ry"!KZ^]ñdv,U`0 -Dzol ?`0-Hݣ V`O =2ڇxӌ]F$M垉`0  @tSmGe +7b-;e\9ٍ`0  k';}S-\ث?`08?xq~?cpfq_?oWi,  `0^u>ΜWÍhcY=?6 `0\_;z'ЫGy#]';rv3̱HeYF6/u٣jߑᆯ`0 ՃW7 `0= ӎ`0 `0 `0 `0 `0 Sm`0 `%=km <__5Sb0σ`0|`0`0H`?`- #ߒ=憚n'b0޿:`ad"JkRv R#=Os xI(N*a!!0.>'IV`h<vk460cӅƾ?]i{"X6Frqi.OTek_>xN9gl"9>*5+)ُpm㟇Z~X<ށq3q 6//IfztZrM?l_snVZejR8 }h0#QSSC?olC}}4ta +E2rLI^&xIUVFL{i E'J{8>N{*)zɝV % Ň&(&-oj5e3*<g [!^A gd”Jpс@8Xr ʀĂg1Q|i,,"o6lh$ʰ?,ا0s{͎Έ)+IA]Qm_[s 9'i(Y\& +И_03B;1k] +vv9Α,96EY lI.. + ҈4KO&YUggI$Vŧ<~]Amin-h.lKTְz*kPJL +VV:;u^qVy +LLN"\3s󩩙Yy59ufv&fVq_߬?d +RMk+cȓ(P~eL~ T)%a;k >B3Q`,Q(#?13 b늀zj4@`[s :HxPMIFOC"t. 4-a#{_2?.)̫1Hʻ 8˅&]/{hfC aOf- +TB|¡U;v աO"U2ݼ Ny^41إx;o~ְ +fz_szo/K>wprmSqѼ#qnj%S^^EFNm\byD\G#Q6?LhQ'`¶_y9&R%*^sѰÝg{6m_1gN#InܫsD'E-~pRs?IoּzN5lpfτUcEq=V?Oh7{qD + +**r+9<n0s'NʘOKNC?]BË]#u +kϧӛۚi=/;{zi}/{ zj*QJ6oA__v╸ם JXd. ~ u ƤtHͭV[ʜX!uZ}(hn_jtSO\3dS@-?z1:\՞}{P]ҧt.6ﻻ0.dDYV(GGSH𘡉W} wyKˏ2GYyUv;P?NtқF1΢e{oRAȳ3b +d 1 %E=5=UΙ(` `VF ,Q g"u f9 [rE6+%n.׼o9{j u5!$ Pi{ە@# "xe!G( +?l +]1N_u^V5,/Ǯts馵i-ckniW״e&TH|aۮJka(^g=/*)yv#$P.IZ; B8n8HH77XYתk +N +Q[Կ fp( R_(DHA Y֝F1@ h@91h7U, +xüOWQ\0V3)`t9ؙ!@$Ĉ#o'v +^M +LwLC[ZZ^SF QuF.Y+ m7~_ԵBҊ:vGip3_QY;2!8yXN٥[M +ө.fT_A/,ksvdɣ#WcN$(Wb|nٲ)ǭ4eʱwh.Rr+ &L* 4ָeh.X>IVIhdmmL.ArQaygϫ_2u=(lԹG +bfkme̱CA! 0|jC^8>1l[?A#ddu1H@+C.0?23*B->h> +@3F]?`' =5̟&gֆU3MM]55'ITN7 +K_ ~~7`"mB7'lߟnQ7yOӉ+83_KZ>3͈B4Tl]/(hUs?{dž~Es~I(I>GQ<z .s!ՂRhVqɲIys?|^T-)c? I&G#s d +[D[;~W[ mӮIמ0 iٹ.-#a x7 u N?6lnv6m i߼%a)nqa]䨻uQo pAٽǕn'f"_<ͯjɩs r@lݲb>7WQ8I,7 G|Fgpfiq6j /~[s {j v1}4ԁ2΄cvRSU@a5@A -f}f,- s83sX24]PY +sO8~Zaw(9Ōe}+ˈ] anYU5$eU7 +s`j ?eK(ߌ({VuO)B#⨜ؼ 7['n"p8Og&of +,v&-O\ + n&"a r-Y& +lwIFITzCHSz1dlS, #j ӵlٺpjrf>n`-S.SpE3:Qp骅>9<ge*X\$2R-(>< hbDs5Uh:[@5F^ +PRX y xEIkCzO^Xʂv"g"~N+ +n.mPK|h=v uXvAW(,e +O?\̷r띇b͂*܋~*eܻ TϸRO/8qT\!R,HDCٹyj1 a@ +'/[m|sZfkggǮmb^ZZ:~QDoQ۽8ѣ(9*q*g=fFf@Pͽ&[D [DB/6zٹy7" Qfם/V 66d}/j"eN}mi}u't7Kn%+ǿA@`(z:GeO0?  Ƙ>J | yY7 +}$3h*Υ;jIK4P~)̤(#˱Ԫܧ;l?+Y7g9먜Sj|톘ꃆ;N}ú^N;wVEXPKDT#:QLc2ds7s'>j > +C'ivYz(On4'l5eu2Sܞ/6;ֵqq7f0!Giϥko+xSZU^L(_rcǑ(4gwU]OkG7ii9e45gUmR+2jDI{YWWҘ^PZДY1(,^7,)d]^^W|S}8WNGk9Zd!b{B_w=U dPV[dqdǩYٞQ97"s\쁶@?=DqO'̻ȅo="1p!?l(G17xuUKcmn[ٍ>zFOwOcKVlۢz| +GM +}|3O!p9D(?ge%<=/%Jd^.s^ddj>1"~\FѕS3:}Vq>Is]lY? +ɋwN" +Ibo<`hРCU.LI3m|/r@y`ѕŀY`齂Kws.ejy%|?{qN<<8)%E.V-Y T +;Re\vRa#"flY`{Bφ7ܵ:OG}=Kb\Η]̐$g4eז49]M~}Ǐə?HyS\B,S)hϜery\^$2e_/OE^I]*ú&[GdyRYBOoawwIwwqwwAwΝӐ>tm +_{672ZN?!e' +yg9trCN]mDdtvNE1ڈ +.K+m2`9yב̷%] K.w;\J Xc?k/0$pROzdz|amC? d[a{\k ٥\N;6cCe1. l9᳸GFDDeoo_x3[V?)iv4F@2b>aIΘK̢pܻm2bx:yZ`)!=U6+zmUިGԮHQ;3<3<4438T64\!HeRYxq͉oԕ痽jPƦ kX,GN'7,.S^ܯ5OzDw:"dJ`㬽盯wVF&EK|`ڿ2Ô)={$KXnZ%$j 9!dqVO?17H:Ws\c 3K%?- //Qi(Mrb=V\J.V&5tw!PN|GS_ԅ:H/wxюRC7go<(ضso|҆!Pr򇌄z\-[fR|Y4L^Ըې 0T,:M4#T,?dû߼v>;|/$[l_]Gk;6̽JnS$Ɋ-k&Vðg!?䤩9nZv|[o>dd`im_ٱ龷6v|gC^G}}|mz'|Go?|GydO?4}d/׮]K^{dw}Ep\q4gOV|mӘ:|}I! b&-"wuWmmmNN__x#CV;>~y_T F MG &HeRYTV/IeRپɂ5KRν١Կ52bR>wixLndM?RDXO]׍^n VjyAuK0ؘiNdiޮ >iVGKH1TH&%]ɱzk$ʅ^]:|(/?hzTƸy`H*?LX<߷5޷Y܀fV|k϶m߰mͻ>^u݆ g,OÁo?*zwd_e\5<0ѱ˺{{#cwUi/,ze}}=y,ǻc OHu4w|2߱7KL.+-b/|z}#}UV}G,}='?:ߤ*xħ;.D_ILȬ-Zv}5!=i[nv/\H^GqqZ߽Q+@z{yǿ=u6Biݿ~I?\ ^%/.3{ѣ;ͽ+^r黍%qJfKd=(}@_/s+7j͒%vLgɳɝJgO7ґ7,{8-+Mlv|~뭷_|9}m6'/_dyׯcM]twКHXCh)_wq;+p +ӶQY1ZGR42{swMMN.NY&th:~JE͒Uް#GSr^t+xI/EMGJeRYTV!վ1g_?@GTS/-/K6968zǀ31FSιk/:dWMm&>.Swhbl,DϷ o3>G/T=_lWt|Qz;wӗ9Ya!^]Gug?$j}D!Q\#<0> & ?/Iou%64* Fb+ NHiTX7o/Ip ʸqmW“SS*/^ +MIIhm^PVttO.%,=Z~ r{G6 Cu:T'ֱ^^ `<ē?X/+^>C]?FkB?_.acJ_-T=g>/fe*.҉)bӺOn5٦^_vRMA /+萍~;5˷ ML~{B\u¶l8ֲ}d?wCa_^K>g>>ڣ}rMoo1_썍&l0~n+>b?澿Ꜭǖq痬i+K䃼;ҷ~ 1$MQ_\\90udo"K%K!II/Tږ7{=^'O +/l6Y#>pߩSC/XU˚D/׋N +d?*e +.M}J&5MeʋJlS4~!o|w/=an۸}֖֚榆Fgn{JӞogٿuϙ]X7;-4SGYl}={ y4# Y(Ua]Ib śsإ`ꉈ' +d$;S_2+YgM{y^nQ |?@koDl o}'g] :emW|knv#f#{?(++ӃSYڍWlkn+ߋժךuɽ)]Ouoy]G)^r{*aiz Nl=D% dv6>qL/rbxlsB*&r֋NgL++ Y;~y?ENVtVƵ5f2bWtk~k#}UЕKtl3!Р4 +Ë JLbJdZ^N#8l/;lZ]\qYRJ^YekU]OEuwjv)k :^n[KWpByՈc\1<TZÊ^yh\p:hh{ 6 ^6 xMKxO_}❍F;w|t/?v;w+k:i1y 0J#5v +1]glJ 8{AD^qLo?GϖfzmO,;Ϊ]{s_n+GG-F +FNUN72f[qYZfBm,>m^^d[Ӌ}̇k- Z[ybP';= OW9=~Ov-}XwrfOV.];_+CS r]o[,2KSۚGŸ¡kwxY]]7nvE_֦{?Z?l!7+:?'􈕅qKcNU ?%,ċ0^'d{+Y!96r@z2j^,&k`V@ڄ摎;3s6 vNS))61=71¢[Y] N߳w=w} +̜09I/wq5O^{=F=yad:5VauȔE蠡vnڠVwɲ]poҿ}h/ao^x]o<㯯3xuw~;Xs臯;*U9䭏+&+&~er$-1]r GC8xn'p)~|*T͐ye|!LWVthIELxɬp3,9`hO +[&Xtyi٩nţK H_);qwe>{fی";ٸc& -ZSIS{t@WMWO$t^Iɺyot̫jWdX7l@6[~,jlrc}N̵rܻgftq݋؆BQAŋR/;#z?5.Ve:/ȍwQO^?kXG-;t7ZWYzr1' t׷RWؼc۷|90\!V'# _r*/ %#f5lmO&HWZdC9׸mxrݐZձAyy;t~0sX+-tg:\=OBol .$'F<4/GwPeeH T̬';& +q=-ڛ'O M__{>LgO}SD^rMc?\uQ2Ggߝ/:x-:!mS]%͞Oz +罴.d$+_ܛ"jl2m-.J/ +l;E0?lI}MS²3UUd̓>E'~/Ol/!Abh89 tp :DN l|;:|dOJM_Տ]kkV}C z[ 9)m +*x)6q\Juz_=h2|5YƳ?UXS'S–\{_8s싮wyMrC5Fm78 WEG_c3wBbnh?u6Y]G MEa yr>8%/~0{MoXjiqjrR\P7[/ =.8EGG;@&fgF$%S#+F'm>hdod9_6b מv / p_@@B/ ϵgqu`DV1 gvEeyGeյu/";$_@! p ^{٥eq ky 1\@@n H@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@A!hYӜE@@@@@@@˅Zw,OMDE>Wb/ B-__?z$ȃa<ٲ/mҁǜdIbL!RA< S Kp%.D/!gAN>ئژTyR15 +|N:Ri_T ǭ +~f285p]I> +K`Abn.8Up9>4abAE ^x/ +_Wdˌ;rFf-3bgwT .fPym&Ohg +E aD U8X1 ߇i{O+b PV= ^6 +䟚 +n<ݛe_]*WL ~賵 .u&)@J.g9)OA֭f0&@ ´E?wFk%O*u_ :ĆFN*DVNe3,4(A 9-' H 5F (gofj)F褪7`&qt4Te\ գ&U?|) P +ZSPDZO?Hexz<Py| +1Q84CijqQpUɺ.#< mGt˳GY7MUC +%Np +,Ь76v10ϲ +yN%YANk"ãF?c5KZv<~0X +.)xJr:UhNb/*ۇAkVu1 jKU?TE,:\(0@ñ +cL0p>?qm)L끅fnV +5a.@}4($՟4 +(S hEy5f5c+j~2Q.*j|ᡐ11s:yYIm,Y d7L' ֨PwoRj 5zQiyJG<7TVgI5bR9>9\1UV6=hu17ui|'jEFQ?YYF rT+x??'T?WtRB +X 3% mA_F7?Ts{|5)nShp& +qcR,5Lax1R {b]5 +@M +z&fS4Ahװxt&x: WW-TMUv +1Af8Ýfp6%:5:ӏ9r=WtȒ &#hW + &6{֠(AE֦6\Rboe~ ߙ)daVȬָװ܎ؼNkmI}䄋XDDOnY?WY?{C&WN墪#EӋjMSRwsˮm-,4Bb(Y(Bʁ^`r̽6=S۰L5Vɣ5=&h-ºeϕ b} W 4 -F +A;}c-ش2*zX+'<jjrzzdyjjbL:|Cϓ|5ٿ&Wi9۵'={0㤥f; +m|S^x3+FezbMFmWf}P3+!gE>5kW9Uvj2Z9mM +v7"(WN49xԸ%"Zk;h" +gg蜥rzc8}X`9SZ?^'c3OlFXҹ,$B +h x~$a6q_R76ְ9mL +H0v Ww`!Y()mRAa\kԪ~l8C̟nrxF=+æE!ZY(F}n\)9QX?ċ\/ +5:Kau) +t)%53Ư]tr]Kb +TZbA~ 80 nD(D_\0*V,-S +ȟ5?FN?ٯn)^!'o'5铯B +$>r 'FKqY04& zd0;n+>% /n +w+UR +-+oçqpkw=1c&\- +j;gRRV1{' +fee2.KSlsKJbT/6b`ӡ>tφVL?SB@w" `zZE +@AEڕ@ B$5+v""omKW {"Hn +K g|7Acc!Pӡ^yj~UFlOr> +;d${NyCoQm^z}u{-1+>7/ojQ uɿnv9{@UR~zγ Nc1[?E{t g  [*P dP-wjWNZ/Pcr5z/fJr$]e1e9 V&Հ䊯 Ű{`(W\flJ@hkFsՍMa?mn P1oAkQ:6> 兩 u+$v _RF}8 +֬`A D2_`Hn9f0A^3M׳M|%q^>~J5X + aGs为mʶ+_{]@*DXPn9_~?tӍP&,2f,r&ɠY.LPp +G(_'>9`0d$2v!ttEr ؂N.`sC44RpE;";=|o-pJLNd#Ƨ w6Z40R$h̟ 424slaB=Z1MrzɗC茼a̫urcyK)ZXUI?ǷA FU;K +pl=!yVo~~; [~eh +;aZ>8@Õ+[`qZȓCw4#ǡViC=ؒ .ǰ} +. +F-`%݄Gh~X \Yd~C WdA*A[%CͻDWލT/-:P2QpY3e1S$wz{-u9=nEzy4W'7-xERC7$ f.Vu^: jN<[/ V >4u +oYXffFc-C29X(C5O~^vY)6T1". *@2 >rKه? +g!]b&9%N +QO5,s?=u`2%Ԋ8*<ݺ,l *@o<,*@ǼEx5)P|F)'Osl6y ih$r,&mF tu}fx`.B^Vbn@}5r G\@i-v~ K +&p.D7֏AbtW\DK WVHfIs Ѓ= o^`Faч}naݰQ98[+j\; +|!qeU./4hFX~#u~o?ZPR'&>LK8&?K} ŊWj`]*pLؖ>ڵoF (d6o$h%n |(^@Qѱ EzCPϭ  Q1|#脛9E^ +&Wf:yC%9F'kq A ڻɶ ]{Xx߬Lu/0z]^ ?}'S[ +P +Mp|/pnQ,Cz_a$?H/+:فav>گ [E ܎+܊`) &P U;MDj |r?GMV_GJHkr# +J6| +ș*Wq̃fѸZm\AE W.z:OR 0;Gy$ +Au{//tK!Ә\BjE *#B%*Bhmj\@:L^KȐӈ@B7ZE+t׍OAm'Szi4~ +P( 膇K=Q`k݌t`ú? +<: RjZ&.@{gD2#H6z)GO0 IQ` ܙu`#94KCaf1N;{L:Lώae +i@2 +ӏ~'Y DWd +sPT?uK +C=?~$%ǭL)@,o%DE׻UP/]2פNPid +`G䟙Vt +pE .Urua5l4.m'Z7:' +_hN)Ś_, 2 i`UE Fua8 (OP\tb}ԤX7D#(1%=kIan(8TO_Z%ꇮ߱aP˚q٬}&0y1 +O{<9Jڽ}4|{ +BK(kЊG)(MMJ!Zc?iLǓ[& Hni +rQδ={LʯwEakyEL|fgm?Hq# +%81Xߛ]!!ܣ;p1pICL^l93X#hSusL?{Jz&98UCoRL-H7:zP\#GIG 4)_ \s# N$V6;JpQQM Zo(8ILǝdv8{Jwi("Gֺ҃0q"s&<ӫ(دc=n +λ[[!zXϐ 9Za oZ՞_>5t-ďUY:mR_!σ ^|.*cj,h'H֭!JE @F +\j1#0pto=Xb|?Y?$rY~uלfA @ +*$S)ö^aamKV +۟A׈&p `{Cg98t³aW|9DOLLC4pLv-zzAJ7c +PA1xU9ˈaІ\u aBәcP'A1!>[TN d@zиI79!rWv%oATx뽴% YѱQ~q} + 9{S/ +GK-u{,@> +]-Rk.^?1x(0)LhAg>']dz+Oe A >W uܽ^1$sG +} a^UR[oC zn@-l,h<=}a鳆xJk䲾/.OP8.B ?)n|i>:k=ß_I6su{J0@{@/jj,PD3M}FeJgf\:/Uu +,1p"w +i AޟWCE3G+@а$֣"+I2 + +9Gy n7zG>K' +h|tPڈhG-A +P̂VjB +asn" +`aga%}A,3ny^{_t_ںJru` .g]1qPMm[~R'(Pm`I:߬ە +/%z?10fAw=1igҥOT2W5tHP3pŻ ΅όWL7+Pv͏&4J\D.@¿Ψ%@;qS܃@',uC7d[2иHw%RS Ed.Q_ )( dRay)c3 m^?&ڿme|Y#%/kr 1ަ$'"flq`0R:3 B]Ut`0ny|(/u6rқ%CP `IJsLX `r +Ϝ\& /2|'G](%F +3A~ +"Ǎ+oI? +i'0kHJaIQD(Ӊ>vtލ7}meT lՄߑ0W A927!/xTk8H2_ O,Z?' ? R)$vT?uK? ׶6S>@zx2K陌Rle +!`y)snՀ3Ќa%?%Ȗl%@  `tyR? ;/qk'uI~9[L-t* 6tU ;C0/as@"@. +%@h@pePU릢Oݘ 0=, evY +st] P[ B&jG|L粀/=9g +]w(KeRc-rOx\NeQ +<'G]y2x.Sqe6rw`힙6g_ qy趼Y9WM5 W#xjtQEOYY^W}C3Saja ;G('Ѳׁgˍ h_?M#|C.,@zk@;XQ(hH ?lܧns.~ + M?͋@cp% SrJѰ郥tM .&G_p,k4 }@.o%S ..|.Ps¬1_dFX'G +'@I7 1: ȣ\Tva SQ + +ί'r*뢜r +ncQ~}a[p5]@Nh Ӳڞe*fO kY"mU%{O;RW)W3.S)уoSV=^ІRT;Ja L$OXkI9^&9U.Te'6gƦ.Rcp =3UT-RȘ!3yȾAu_I} 2l{;xD +R]*i/~W4 +H1a|S![tH`$ߚ+]t>3p +3KY٩tOV!` 'QqVtx 5bdmBzd+_nh|KX!`7&cRA1sz G@^Z y}yk_/ybHOR)*?iUQEk6RBѮhXِQ]z졲h{]M3n#K"Uf7l=E^n":*> =5uo2jЛzErw) 4z,!o_jzwdDIv-*ߦꖺ=. r +D ߇0n} <+m-_Ы0_'vw5A!owM.\4~jCrm'Pg"l0ޙr+a,aÎ~K$SdBQ/B)Q?"H#Y Yv W(:iq +`T6xj +sflު9󓹮Ruf_1mTh:jSHDZif){|>.]/r +U/b[.8S*}nOM3 + 6H9!n$jM̲d {B +j~2JR=u7@;AZYXIC Eɿ\pho} Cwz9 +zQU>, +t57c핟1y|,YbT}2UQ8A-X-l2Ƭ*(BYT+ߚ^;qgF +.vUG6Q a|zP%z&?BA[LOѻY3<y+쵆0TC +/B&Lq:c)?4nO*`+g2TH}7i u뙋?J %@t?0-*aR5zgD-fI,"wuG-F߃AR - ?``Ƀ/&_)CvqQ$7.Y'8Vkߒgcn +ph?=Ϥs҉%5zMk>Ȯt(j!Y 2ݳ4Q^Tp2DZ) ӌkb<m!~ra +@s` +e: +6b4S(La!QJBZic t +O͐a֑BZj WaU} ˑ,J; UAV -@kY:4R]B﹨KPA7? +H\y5` + +_9k!S /pHA1E@#M #]A@ʄw^|_Nʱ܉%UDt?G/&dY??R *^Kr(Q+먵b2: [8؏EyfgsT qv o D։R׈N+VU@`vƊ>/-1uǡ-@_ .ƢK +} IJ] +( i 76IΟ|DA|!S@1 4rvkLBP#z/P]fS_=xpQ7up`]͝U˱e|(7r{Fd JՆ$jtˍǤpbamaR#5ڒlگhq vRBފ. Ga&y*RƀiSn3vl}uwpQE5)m!Vj宮c}CD6Hcan؟cϗPHC Ԃ v(ޓ{r/z /*2>9c_57/ $5ï0n"Q~נje&ޖ{%_. +0 g& +Bķ.ed>FE5'裮~;dSF +]8$P'G! +X1Jn|T %ЊJt)] /ǫ#e6[;- +#DӠ[ x~-; +_i 7P?tPӌрad ^ +i='7 +u/c +%Ayߠxf,xw![~+%_.Ԭ lS +@KPh?py~'tKwIħU@Mq +e +s\$RjfYiZ*n0 n:;'L@"=$bS;hYSڰ_ňYEocED-Kz$o1,1LIs6 8NYc(cN|B!o @t蝓L.tk{Rn1To ,D__Z&Iï#GC/-X# Jt)] ;s+}@ʭ|.߯hԑYmlWĘC|;pJ&%T<ūN[8a +ukkS7bO:%H +Sm[?mCC +|)HbVyvORkа"R? &? +0*"4`ЋDzAoXVKr8<ȟepj=? +o2zwor?'Ka +h:PTFt耰>22V?IIp?]ugb +x f'[8T&ܷ0[;G}.=uzY|`V_!Q&B~NV?yG6F ^g*1ve䍊a2)Yj^/ma,- +xA(L ^&PYFv ~9tcn-5yBh46.9y`_Н Ǣn(]#ucjospP9c^Wt=i*ί{-ɻQ/Og} o:q~2C (^!?}$KPB(x'E待=٩ 0u6r?.;tr+X&l5)/|eqX؈A]m Nh3'a~[iY;g#d? @>or` QZDqQpSu2[K>) +/?W&V +q +O +iϺTX &:fd蠹LX ߀PM:7S;:8 ww]ђ +we۷N>?R +?> +('.O.BC?E8#GT><#ނݶ?Jj +ʾC<1=ݲd0v>*z +-ba +ֱBy<yAYdsgīK^;nQ>J:FS &Aqo.7NQ0Huq/A]ArX +Gwd +~@Cߐ}H +Y'X +KԠYD:BA=Dd%љMo"w!Xk9|V$.f(2GΗwcuD$ +~cP czYe)Ut!0ܰC+F+ί*G<#i(08*C|W*Vy>Iw>_TFoa"^D|F_+ҥt9DEk) +< +C"p~k`m+,=h{l![4 CpTVw?B- (28p%@@VD +Љ])x1 (@%@WRb*A.1RLɨD$$Vu7hF5zsjw_+z lh'F +F +&_@_ϯȟ%v /]J dR[w7h"r,̊)Ji%YO;cr<(cX&yOG%nD!Q^-ҥt9?NsX9*Ū?WX>I`+ 6xzf?pc9%tm6PfMlwda + L t=աlRW +а;UPB + +d2* 肜l +\֠ں/;1}`~XN7A=O->4C_F"k׷Չ5lN.F~xJcN%A% !~xg@oh?q ^]pe'iwPT"( l*` +AJq?<ҥ٤t)~DF) Tg 1k훮4ܡ 5 +[#afThRb$?Æ6kĠrMNOA?#P hX6[7tEp[R@RF +Ç楫~4\\جQ9VT44r! o?Sj)m;  vw&$KZۿZ*ɀ Vu +8OAοe~)mOe %z^%st?_˟7ϟwHkVK +j}}4vAO@ +~Aw-ҥt9c̐a@`~BuuHv5ST _iRur=܍e + +m  j#;kN jZ4 +8F%iTZ\".i9L,B +Ւc\۟x] +<[ Zr +`3U.nA=s;R +/`uy/Q)>W+mF1-*7rjy|Il zP%~<1!ݸaTjmdoi7ܰ`LLU~,4q7o +ny+j`\>ywfǭ\*C' ߖQVKE"3kh̹&?k`?A(6vwF];>q\R8gsWPe +7Ż7-JL{I(MF)J#%V +2UoէY~) +`g|czEΒ͎b:J\'K䋞= + 4` */̜&gQ7bKY:7ds _"0BmnCL1XTW P//5TQc;( uۘ~*ejJo7d>߇Hxz {oa=zxa|Dm}V>ei- +l~#J +8&P3a>?F? 2~VKr80@p,dswF.+HxJ-pihWk*ںI> !ӂ/!.D^-`<PGA_>BRЪ{Zn +<EPSlѰ?+J/p$-.0K0|s?sƾ: 蘗eRڍNS??cȿZX+ܥ5"yJ?S?ρz^˫%J)4aI?(;r?^+Mr)TG +04tO;pg?Hfzɱ~_mi06\ҡ}Ryy4r?sdCcxuG5㞑O-ZĩX!DFC'&TuǍɯ8D!_A~% +`/5࿉|Tzeq +߿D^wkU0(@1Kt)]OR&{.e쒭U=vjdk_|U. Qsk15\3KKP@RplもŚ%E[xbTdI⚠ F!OPx +GЗ7fZD?S.oSnޯrHv*(忚5rԷ$d[&0}+)mHj+h\ +9xGc`g: ryMύ + |S|xo +t 7 ?褲CnNyڷd/QB3n?'5L@: +٫al8aO=V%@j?( yr0^D( Z׌iqp lU,ry7Ťf +R3b? p]F4}_ +oGyP[ W[}%k=`Zo;]EMq%|oJV-@3{%l @)h~s.NN+F +:ejQz'+0YE?-vGȎQ&#Jl޷'IF=3 +Gۥu`F0[N(ڎ&qô*\Pz'w 2Dٍ$ + +pW#~m>%]3@B`KܾER8=ܮ-gC5 nD~=ß`?=Ŷđ<,PD|$y._yU>$`3XWI#K@`U(Pɰ +|o$k +*}6ApW=JI^ګ Zn isW^st'KPvBM~˞;/wq3.ۄLn1"yGipl!JڬA~]*v@af9l +G +^ rrDQؙxelC'\g +3I +цaܵH*x>PT;(G/BG +:4QXO ,UyOFuhA,0+oM5kg +6]N컥q76 lΪE}܍b?<& g%9@+r T$Ng6̑C.tm@8=J t +(bό;BXw0WηͣGWNVcX!y*T" g,76"zkϩ4O__}EA" wd +d ?[OdR?Ne>P/vє sy +MZ%ȁ>)R) c1H;0qOnYFjv\ +!Eb:,=ay#8Pt +xHnD z7h/s+G>+/-o +_]6uoyTD~Bf]I򘚃9~} чIz|p/̴`F>e^1Wݲ|1><~F^([4 -fR| 9tod!ǥA :8QY X}Z 𓈥-Kt)]_`otUfI?tQ4(4vX¹c0 \4#JjF +Öf 3ҿ7wz^L#n~JCK@3NdT_SP +=x?+= +Cwȋz +AȽJZ6H + Su V"H%e~@sv +;># h/2xN:HyUDip& /BY +X-Ê7\ilޚ6%RU|m b\($4IGY[Toݔ'?'(^HÒ'}nϙb㡕Rj +$16PD<æeJF eȳo))!j"U_1װY]"Z|oE)ïI-0}Kիy  s5"x/#Pe0̂Ґ6 t9}nPLZTib(;GD +N +-U6Z P!j %< l%q ߈5|G@WWqGHzh4f؄jEP/ + Y}bWIlw9lՌܓh-e 4LNv,,qF@gw'_.@OPt?,wJ͡7 ^U=*$Pׯ8tM9cuTIJ >o?kKY7^C"CXJ>y|j=mrcݍA(٪W=Cp5'^֭=$&VC 'j/R4S$"sHt"'jIAd¯DT-ҥt9<'[gkr MW`{xAs\&&{emG Pt[`kWl:U7>Ob~*gz>Dkg'.@*e +{A;gȔ +G"dHtx,-v R$$F\pWujpIa|'ߖ{Vf=Ca +2-B9 +}Ai_" H +ȅN"04 +ꣵ56y(kje ++?To2 &۽!\X3OdgoN*J}Y!u|79~1N@2{J$}˕̤Ocؤ^[v +[^(qz(| Tl7sNS5\N\9o/07߇uEkXR?,ڧOQ*$ +kj\,Sgs?q>'۲v4l rj.'JCrNynۏEfJL}<%OMۊP@0"?vCiYWͷPP yLHhZ.X|>Ƚ3M +*B=sib `[: rw"5o◳aʹA7TD7ȸ* +Ƃ6r|n6'}^XhpG :Z>h? ~-Ȼ:7jTd +o$G Ȇnct[d!}"ET K9 Up50z@>I9{HD? /;U ;M 9 +}~ k3g@{1~_ __J-PKr0'e6QdSSBlh̏n+Ad e9]>l +OL+ogv{7Y5߷;ȍFvFI| @ ~ KS q*G^Q]*`;S6xMwD٦zˌ\>gʺ{ P ȍ +?ZC0d#*TUԡ 7BOo7Y'w/2q\*|[UF}ZUL )MAGO@Īa:<,BhAI9꫱4a3S#jO'eG}g2!5a~m?ZX'uF?F@|J?p/i*?𯴟{ [!Gz5oߐI4g,M1T +2ZC[4*mxʅ^U+9?ǘ5/̒ѬέS$|UxZ¹]ُ7bb}"Eg]dQ%f,wN@"PFS^™\3F?Y$ Z_tQ>yyMΓbp< z>gIN6d +"L׻ .[[r7!߂%{7jm) L!3g +QQ5v>&η'۰b+*˔?G䘧/@(tUKhӃEJA y# O8f>8>1wJeY-`)*/EAKapyW;->l(4| o'L%1=Ϭg!  +FwZlC"!߄E*.r֩{UlqEbMנ F.L8Ƿ!0^Cv9Tރ x[?$3? |<)%V\ +Gs6^ R#^ސp!K >L"r}mJ!)W^& C쓍kݨ3TmGU|r߿wPutN;?Ĕ|#ؠ`a أu˽ILln:?2X! +uIbC*Yqv"w&Q@a#Y sQ%@?ۜO#_P* Vu/oFEA PP@Qp ;逊EV&Ga ݁ԭcrH?K.mX^y>́ϩn2!_mX0#Vڿq``f:Z _?:ZPs*ڔ|J;B́i6|e| P.4oϟs?odOo3_ou_?֥(0#~@aCwe{ekKL(WݿޯɕTw3Ct˕wK N'wxp"̝ϕ +SWQ+wsD CK4;P_q(l3$3F)#7Q95GQ~D񿦀uA<ʁ9BZ u+B: NäW3G [` &ti0-KV0MXljn {Ρ%ycK< R44bOlm7F`ecoҮڃF8#QTHQz`<ĀFX*+~&Eǔ5#uU矟w(Ͽ +-_8l). +kbPc(''Qmu0~Z otkؙaf^K&+ +;_E +-O +J_FUp޾Gi˸"&!D`|pr\ 9υ@SϾD Ўy S ŕZm22)S|X5=N)TKS¹ɬaRu +ol|!En7v +1X0 Q +ȉs.@gf䓂(4I]su'O1&_%#%@_1ZTT@3:R(Xe /PR@XX$9P +q&0ƝA|&du`|V~ +FZY2rūD4*İ?FWjU*&>s Yuj5~^")@3<痮0CS迾 +2UHu_?}FQϴ|#v߂ Fiw j.ME +$K<(2?+vft\ Wv]*(ydo-?Lߣ0eQ8M60XAJfv@B;II!17jrfV_%@ +U\R9&/]LC]kn7js >ctaLjFP (1f <"KPT)WK͌b776?̄m#EYw%|A #󫓢(/1Nq\fNj#_ho|B:qvQv|n[ 8,XK +0@9 +\mWtLQҽTf +tDxڹ*atw)w?Væ=7mPkWRNwKd"p},4$wsm%)M2? $SD>[ +2QNo!l phRs5< ?э +)jЄX dy +I#|!P@'0WѪH1D^Hr?N$`~ >Y|T;oZ + b<t+YD#oOaNxڋ'鎻UD +#RPU\ܪ;GA +1į +FoUzJ>s\Yv R)UoTJ*a i<HˁZu_?}z PF6Q;a{z$} +߈CY-R? 5X]{&9 Q}w#9̗(QOuqq^sEA(p!tePm9S}qmYN].`;c +pWU(w* +a pGP_hd_;\(XG;SA597k% +vA S6.f)S~X7o t )EO|Ϫd--,{#S$yyBήΑ9GǸVڿeC΂)X +τ%_ +xC +9\z:>ҟë# ++ ,c>tX g h +'wc y;]y7* +pt h +055Rmr.@3οE4Hd\Qk `'tu + KԪQ?71 L`t-CY_ y.S6z02. ":-.CgqwP˂[;3{6Q*%["_qV$UU +Cg׏q3W~3g%I b#\A.I'W{\Ao&sBz-i_"A sYwC] mȿ +G +3Z>#؏ +=vkFTq`3,v2>ѸSq*zؑݷy~e;q2e'(}+a:*MnfR?) ` +3Mۏ +ZI#`˅(bFI>l CuVBTC 44&hv㫳fQI޹E +֏h%C +\{eV.@W!@fqݲ. ;'gHR +UwD ܂#z9Q!& +E +v*-\kk %aT_Iɬ;RE?9EsP#L =Xd1h!oC]:zz f TX1cWmS:#uRP]Fn\(0XX9B;CBw_l/QL* $wda׏q?=i%3]4n߯!JGvHK V%:e ʗ`32'7Ns[/eoP!M m [Un܁|$6ܶ"?GJ^c;cSaI:낍T 4r%Di~wk)KRFTI(]D>=~wlEz)Y?ɰ40:%@Th `:_ ;J(#l$4fcޛP/r!?,r3) +kjz w@/1 aJ Hcy4l@G` 6u/#‚_ 4:?zJ);$S oY3:=6=`C,K < gpZw%xoY o5Mj ~{~YZGgX$& +@='9B.Ƴ;] +Y +!*Uf +my'1D7*}G +G)@:"C3=~ԏ{[( ;;i?ѭS֥U{$?5(;GW[" .UТ!!L7w,M͑B*v +D4(*L3sn`'ن|S 0|sDm6A^?HCY(\~-A`gQN~ N_ +⟻bu_?=01F +P? .Ȗ2vjq'+1Nʯa2NDɁ#/Gc VD +0@M N(i +%@׏q>_(@?[+{ 4\-~ ײֽ| jQ\crQ=Q zJ rZ m0z2 λ72mzƂ&߆rZgԨ9O]ˊ.~U +3h`@F=HMe$ j/YZ^[|Ky=Egs%U;P{ (J8"о~F 4ڻ* H.NTÀm + oU`U K +Db`<$覒a 4~hr0βH[)  {!eS62WPN?JHqH 3YO% |(F] +c++4 +(_wۥPG@Y@Q |ˌO "P׏qO'hsm_{*KJ\Kr祀fikTaUt + *J* ǝogN1 +{-ىv82)*PSHHJ0/q2@z8*("misSW8sW5,B9g?Iyn +S#̳U"Θ)*ݬxc;/)@+}ĺqɍi+ǀ W SoZ]HAcJSpˆ藳h/3;+/q^+rj,f3+'柊 *0)kLϩ:Q? +}rJ$G&_qOLvݰ=&G_-? +HNr> YB9~ X… ^C&hE7q1>1 +)O}.ߧx~gI:[aE_y[ 6 ocS@ YDcd7NJIzy>MgK +ˆ^6cO);G}2$ L~z$ +NT P +`G_f8??-ew.wZk׏q? # +"x_!*U<S;۠ 80ȈNR݋T{bCmY&͉mo,Svpؓj?_įHjsD oŹxZD=GatғX &prq \ă +H4}7-M yrǷyCmY\F(iyЂkmbW39>[OQ_'iƺ|)اT +wlgo2݂ 6ď-:0fk\Q +v +d +47p +D~5A59_GfoGlP!TFziS52 q4|aϪB8AoOx:Qtɗ +8p%q* + k +]/;uCͲCb,W{Bqh1}f w{j" +ZwM}F.%lTAxJIdtBbX|,h6, +)ZM&ƖSɳt~lS?:&sP{1J)z7ӌ!\{{FͷaJSR/dsKx2kS"Mz^4TQZ8{qhxAO`#Ul*H" +?7 9/=tqWX +hrwO=1+?LBni]P "w6c'X'8$o}A/ Th {VOrQ@4_~ԏ{p׆˿ <𵆿A낿# nCSeB!8LK+=5?.;C)?>p khy҃ZYͶ0% F61qjAGd]V> ߅hWCf]n +v ≊P\ ;@7<ʭqCQQ(ٍUQ!?/l|A+4i.lO:߻$3]nEm=9`y&mNH< +]5ȷFwB&%\..SEK#k*&*ڱӰpQOZYdHXqZ-^?O7ګ +rKGQpAn&h.ES)S,VldH>k8`v +x;9V UWlv +iH#_Po +UULFx)U,eNs%Ԩ|!`@eJje +#*pTH&? +ҋq\8+`xF6Kc cFɱs(} Ss%ˇ~uuWvcn6\`{`Sv9sw%g0IdŌyn]O7`={{;]cES/P2#,uvKpFJynh[{l't5O"Z ṪeBt{)zt=`<CBL_=F +J;oX oQhLuo4qa`7,W|,W@J紁_@F"(4LCyTsy-D"@Irϡn}ߔw~ +znILB|Q!b&5s4vA1c3 + +*59#%(^u02F[VX +K 0:NJk%f)#,ŤMP +ub :+Ջv(yy 7 \ O9rAYAkc +EՅp4oj'T S +1.UD9 6'j~,FmsH>`n -$i Ft|'Y i? a +=QQp7Żsx;Mn"`~VX _!Q=ƐhC4Y}>Xf8m8,)?)C=r)+G1 +79 +RFuCpQ6\]Se,?Dfi `# +?xN|V/F#f3r@! +]3pz(5r>Qb +kq43P/e,*%rsG3'' +[u):ءa߹~"*($Q 5_=I8֊/b,а`A[|+4(v9,`.(ShMd2Θ7`&#v#}MwVQ )a)6j[?Ǧz)PSJtm (YE,|+pgT>U+5,`u9_]ؐSIcY"a +T@K}ZB>(9c[.H.@y>? pv E2 F +Q?_6\OSBzO6iXhY5ӝjCbGAǚ9sA~@~-i?FJi_2 3j +PVq\FߕLQ;RB̓7_t{yF1?b}09YEAs3*|Y RY`a۟u- +5L{ZG[;T.m%9S _ܚ`y2W¶o*\}!k⨸,VDMvv + +~6b0!Z +YFw jG(/p2i +]A6S9K^h +7g! +@]/ۿKbׯ؁%yl2}oSLNIK>Ӟ>B(2 +..Ԡ%)D`R!`S0Jܜՙp5_쏨^i(+f'dEĥyonƛ%S_+/l1,ɟ! pǝW 0F I_v9вC#jCϝ}"ok|CGPT@,>'Gv{{Ki+^%l_V}86έfX sr| H9MNBwhtDzB""P䎵FɉQi\I36:GRCT-M_Dxu1(d1˛\>j߆bz/5 j^Ѿ7 \tY1x"_?*=h.N~'q`,?@ +5~ԏ{-^u{i&]]@5 +hg,'ҳ%f;k q0?m.u# g[ByP^^SPZ(Ej\3pJ9x;+gAYaPe֨f M-#gy7F +ɔLCt&;Vvat.ƿ*s] +*;q\ Ä 3 + V=)hxIo$gGF +<'_ +JTh0Z: +\vqݠ԰-hpʉITޠyC +r ȷ +k4M?*@})۾@6v6#  wg7~񋌸2H! JTX + .P^43{ W!;X!>' ]EŢw +"DoJCG'ch෇G~ P:5 喵) 70l(Jl#GISEc?F_HhºHJ:`)ɷճt²)*zJr5`H~ +Qk +1DE+?gD%7erY[ϯ%<nfۖG/G? B[edQ8og:Wu +!D݉~FVcݶBؿ떯j +]xaG +X7 +5[&u8\ OiCZo9&Vf%a^jEv* +98$M(uޅ{"h`%eGB:?dGδQܡY +hhFI_K +@M^ + +|Z±v*}Wkg)R?]|ȵ)(˃DR*(\T +M +X` ^@<'$i />#hbXˉ +9BpYZ ¹v {+ ڏ2?sd -olۘn|5GMaA)%d';ۊ +(u:1w{>~`; +Q nYpTwϰ&sۤJ3_ i^qmbVLQ8O<WW]?ܓ$|q?2y ;kGńMz +eWjv(Q@oх'$Gjq yQ 粳<]Gb:^ D{,"VdX2W]$HO4ڌ{/c^2Ku~ϡt5#] J +4d +k^I6}2wJ/Ux+v0 -W3W;nJ-nĔ +s PMpF3K376J{K%;HY8` Ө찹Ý x&ƳQdy ,_u;鵜A L}W]r +rWQ#MALd2L9Fy9@mRSVRGi'iFܼaKn엗6K#@ЄLH̟Ŧg5oWGP}&CrH"7|*(8D)3H [s~}MN"H}v A:b/̟BE>bòk2?sdѠhE_wapQѰ=Mzsd:i 5 Z(6~6h]y#x!ZV>bĉAi7X?'K0̖b0X@# 5 K ;w>'GUAZQɍW( xg%4u +0Eb7? +T[w>T0.Ο R؏$#,3G8Gg#c>8TۘByv!룈p?P*&y hxwV't}+o&pljn#xi + rʅb<?_Ȁspy+can`z~{-!b4ᥨ͵Rq^-V4(&]Vߡ5޷r"dFFQ0Ӟ[ Y\Z`9O{HmFu+w}D~,ڍ&ݍ+eL +EOv4GTb7;4@FW~[} y:o + Q_r;;R<F>BCg)C . 8#4ӟw:Sj]-J?l#hSE|z(D .4w'JuW^:'eY%-YwP(ΟdLD{T;<_yFb~G1k]uӮұ] +Je&#M,q¥z:YY!P(Rk[hFսTD=62Rs{ +`E3ގZb2=*5K# +-"HA7b:[u +}gF +<#Gɋܶs_B Ʈ&hj7L"eO\;47PBEeG \nj/kA+h!6f߻.@bLu3ܗ8 + -X74hA3i̳rU2t;lF5 #He} <DqvXӁv`J9RBVgEF%-D%/kT,E5{ :r +A0 +ު]᝾ns~5%=*6q +3wՆ+raIqu(IV55gq#يeRcKӰJ1uu0"Z9KV3E@@Vw#kP3 0StTB*Zw<}gFZV.(@ҩ +(" ׸OEQl"@"j鞆^7T``Q[ S3v}٩2 [A9>ոJdKXk$[~&އ,j_3\bKݶ{M%% +Vm([ +峥PO +*U꒣eDsMЊ[jC`|dquQ*pz/*yjI9&:qcAbLYt +?4e,=Wu=C6U=I -% ?,y#6J̑s%'=#i>ja[DkGaS~# d +.^|[D|2Ի/xB^zQ،-S]R^ +?Ȏ9ooLپZnh6MfQYr c3' ??-ןu?ctPd.%f(~^_/q@ir]q.,) rAz +|k);g ++)b\ht!E85ׯ,\K]&oʪJ2e (( )92kS +)viL["0^S yH.~6 +P+1$+0 aOR}vQLm^7hA]k䘤̫_;*ohdp(1̑9"W]w' ׈1sʶVTÜ?W{P^'Y|'( Ze7)P9|>{",C/BGp bec6vlKuG +ɣ<qTxQ`T?y2T mS!-9\.&CO {i)q%@۬s+*2 +G2db5siAi `wgr15x 7+(_ 2>M +t.Ե(@5XL7P ݠc@V_ٞCJ}㪛sqFt\K +7gOծh9E-ຣ̀fzWP~]=Bz<kGz`[5aƤ(!&[>Eفzl٫ +Z*>X +OP! _ON|F@o*GO2*%__WYnsE]32$\ר 6 +)x%5 +]c/cүb"wdZ4B[b{ i + ;,l#VdPVL z@hH af}+䕪3TLLA q +pv6 +@}bbcxA6(DW~T9<J=B Ӥ^KMpW`s +!/k;B + +$ +^@}oW$b+Q?8O~#/2n9d3 99X= /uaw Ilr?I䟳p42uZ6<@JBŮN-)WMJ\#}3g$qlrH +% s~ _O=׫8(={- n(r-XR=$|AkW!5n~;m q~*w!@L^4U@ ܊2 +ІoSypn^kE9%oL+]*PVZ +s/uC G6 _KX=T骇 +FL&<VRTulӟ-y_t"0&9$Y Wu-&/oVjhW 920QiFi@S RѺLJ[G"Ǯa -P$ +X ޳Y`wd(d"# +f! +ɀ(̟lآV1ЕV]Mf%tHݠw]CJ!H?{Eχ[Z| ߻+Y4D<󂷢?m +ܽNF|C=#PLyA?,E]#ѻ@837.rHJnRI˞si]kbv@>4u +Y@ }q\'hyρ$ojj\RP5 +%$,IYC< OIWo/ + YZ*C Y/"tL΁#t{ +wAI%ZOĖ/hS; +pqKI~`AR@X@l +3^9D:w4߸67aБo5EM^S; +q* `F +! +Z@+6A}Cfqi oԁh^- +w> 87 tER jo +C/Pe2}O#7 +{7XR[AleEA-wi)$ uyJs` {JEd:󤌸HAf =@"+t$k:gjoG#W8F$PcW=\j3 +đe$6?T~h Wa@X?CD<_u{3K* *10~'X9}Mz%j"JQL.X@ƹ2!T| +MH=SH(`<|>uiQdQRީJduRW33Mk·.1j€*gM (AxLjQme.۩VW +Fa%`o)79`-\TyE\D +X$x$MX^\ww`%>hn3^ +ʰʉ:h@=@-xU@;(C򍞑j9->}ʪ6&jPz-{u]̑9s$ +9 #I]Epl]=Y 8<8* +s Pƅw!Ȝ0)So"E֝>~QH ( $/_*^XbZ:/4+3G8QΑ_`&O{Dv쇚܌n2)±^WW~G[>nWLQدBJL.r #Gd퐼Ç3uB0YtDg4%o0P8k +&n*"K +̐v? '-7M!_R_'M.-VrpY*v1pDS +է\`R} +@QcwZgq6w# W oeCnBWOA]'Ʀ:a.=In=ڐO'#%'Yh(G ?Q:P:\(+g0]?I)Le_ZV|FZP se_/ܓtfI[r?eunonn~rBYR*L[s4z +P!rS4 +XipY0V\uJy/*@mRQ:{7װ}wZN%@:aW50`0 Dn08?M_ٷWM"'kQ_+]}M_.)@@)@0T +{IɮXyH<)@Go9~F9|"F_~ db{[ޔi>Bimo3,D/DߕuAW̑9Z,cM~M)@!aHi&2'&Uw}Q:'+0j=䏒]ѻL??dwT0McT=L#U~0,M4=(7/f? ,R`Q.ML78 +ʽKmE_%A=J R{5tT|k!>FKΑ:I𛛳pۺūFF> h JI \]IcHi#r >}P2XStJA BƯ`MX*a,dc4Hޗ:Cv"=EV¢ ab԰ϐ l|7A@8HS(_w4nL2fy/?F +9]Vٯ) +RӮ1)7o?_ҕ.7߈M)L-n"F _!ˮ(CsQs`{x-?d\*(ϐ$Њev̳T0e}9DU*Gu-j~Z04@I^vRXk<=o4(\V+G^Mn +dz(/;1z:m +@~#6FdxpkX|jnF )"2$wYRϿR!"\/ +X>\[#ndʂT(vFj +Gn6QQfH +q&*[A5wB &/ԢPvE_o$7Xp~$: o]} +(@ar+.Pqa_Po~뒩Lq +MnC]P# +9IY:Z0~J=@&=& +9& 5؅WgY8=Ok`Hį|Ёcf*@fh?T4=.!p p@g;p +9Yɀ0{X#45rEH$1\=o6#WDy( ou9WڼopyysNۍQ +(3<,h7 + `AFJDs@7%Ʉ + +}]#+y+-y\վA}~ +rAg6yy{֭&戮?uc/Q`\̻Yɟ2,G `=F/ ڋ8HgʮAR@X +)hjؤ,6Vl +1ydy+ x7XpL#0Gg3 2sŴJL =/h' .W5K޳ M5t#t~ +qƣ; f>og! Gu"gW"0~twHdyU@W^y{sЀ  #ěSմ)9~ & EQ~>W70׀K{7%RʔWW#רs5XeTöAp.*ƋNq~33S}د<LbN()b`W4G׀oӑ>XJ-SBpQCyxV5@߄`*e`<HD/j0 ,*TN )B +N/0/b"/iga9f `K. 3Ob<_̓PxEt|-sbNA1H0 +ϋWJi|\(x?wI?u:!j]qC%ui|Gmy 2(⿦B{'0pؖS?7OcY{8I;Y(%Vk /IZ_a(]HQ +ֱ/_h.Nj `=#&hDBQI*(G +cSx! +a)()XMk +6M +f_ߥIVIPWˇV$YYYt?Ț_>y!j. { +I%MlP$:0?CM{ogo% +)g:HOA,@o-$^Vxsr4Djн/$B7nrz*"ϫZM$~뚙wFY=:Roű (E:$.{aʸ ᜂ +Ȯݐ+y +&-##a8aѠvaS{2A8% "P."2T!(fA1SD.;tiBMv8`V,BP55~W%#\h9C̑쒃 ֗aʪWvoN~ԁ4!T,` +~&#oFCY92q4 +x#PXv Zv\vS_eળz_?MdXֿd?ɕ,F'K w%납r +T-0* }H30~#X7F:S%}$p@TQD0 lO;YJD)Bծb +b'g(J)O%?'|5<o]ES#O 8 %LDe?+kLa|[R]U˳'Fz+)\-Z9ko,7% tf]oPUKD\`NTJ jhB +=Ye;3bpmGiBF32뎢q`{W0N߳[%?BP JrA | .yn + ,+ǭHP8)X%٥$2a +_p.{2{W nW!HfH\~D.2(뛇L[uѿO `7@^rvOBtIkV6HM/Aow[]Å_qWĿ m0 +?]G-|R$_ ]&_^F-C/#Q)\=`τ3穄j;`vc^BKxb%-cEJ+mu*fDnM.A;MX9j3{0USIBDlT`!הF]E + +< +*A׆2 ;zugGxQ +&A_>U+ ms'/^VCRcͦpp",wɰ"B +*I}a9<'=k(8!yMCPN R?Vx.,BFDWBn~ PKWSzCvefn+ae8 J2J :m| *)n(֏\@}6uJ*Lj̡@bdfoKuڹh#L^ ggIgj/!߅ @8EqQY6roOE$ YC, +/O% Md݆TZ4dqd + +VPԠT)B+"Ϛ?TL-e3.kj892 /m[Zq:VXmw~ +]B`ul/߆Ys\62᮳r*T502bn[L,.$`3O~`4<|S1Q&;fU4 +df_6RU7k_2A֠;ѓL + n82ݥi +Hdԟ?tbN |$>9+_}yrG, +B2* s Gtp/<=g\΢l#V}'sKOŕTJU'IKnaWD|tf{y(Pץmv'K{Vl"/Kwѓh* ̢WPeRl+yo* ǮGDaH #`b{hZ bI'\>|bN80p~}ck^ +^}<,^r d:-W/(3T<>}+> Ie15Pħs~ZӐ[%Oqv.*F +j-_(gLTf5ӝm{?ʢXkɝd?l3ᱳvF+0'? +FA@%L o)eok;ؕU/P _Cv!媤rr&&ܰ2'9Ma:jRU`mmң tr$v dVX +K\צE-G{ _Amo{44%9'(]gi'aoxA9![0#ЇbrFȺ" +h S +}5 ȤC]2I5ӲL/dw +2gwlHM1֯l!=$5M2 ~LRН*  9@$TXrwJm)9+H=:FܶLܖ]&-.1_&:5?vTL~I;? ~]C|.H*D}=tqYތ"|bɿ%_.C#x2͂ +yi!% +'!ġ k e +-ie Y* V8X +>ƨ +$QUur$t*%P* +I4:_%K?qǣ90Hi>lgK8*'H՟jv\墳U!+;< U.^3Q Y[J f )!ʒxSYq~~^?_R#7Ϸ8*%$xEUO}K#À +ſa0B'Ŷ#w,LdNB~dq[zwӇ+L}d!zkHX +l$GΣ +DMmai .e!y h +~Ej + _ϔwad4Uўu!zclۘk F@nE؍okk0sYL,6iʒjeJrǖ%yB +_)Џt'+>^%@t'G@B `Ȗgݹ݁pi=g#p?ߟZKTT?ffi yY(?sRp{!B[tN,] +ks U(VX#p䀰T[_9?BU;T=?3?~M 2T-%ʮ% O"җS,5F&2~~9eڪy0&U"1f;01S!Ռl%6h+OPn;@bkG7U EwZԠ_O^ J?m~?a 3B0kssNz~m9‹H +Lĭ5hCb̀0hg@O'/DQZ +M-$Q2qO߅n=X:5XGӺz>-egk6TKPWSG$* @+E5 +m7eoC&T%f1\#0`a)cs ),}isڷ< +BIrB@<#VKAK:zs!jQ oz?&|}A5\g"I$6e +)0/@qLoe5QQWۯXQ˟ёɼ6'?24%zYR~g9% +xF`4ݢYzk7VfYiŠj"ӶߚGRu<;hS*2ŏ*h,@'VϢBoI AlnwB + +~~Q l"QUi`?Le>̢gהf.*w8Q\ +vk}!y$ oMBD` L`)(mif }EsAh/OeȪG?1ܧ'SLdCһuXw} +o +V^: +Sz?RѴ/<~ 9 `_&V'gjKpM+y + 1HL)rVtH0{-_Q|Vzw_S4%>!/~ nō#*1h؃pp+? + + +=|աo .PF +2JsNAC"RmDG=[Dk2G[Wcv:iEXs?܏YT Sv4mRBG,9m0|2Q9w9`}aTcP@MVW)@:2V2˦ +> A_̙g 6q9 ++pWXR>1cD(\I?`lB +&$lo;&*_1? K +g["_>5#6SDžU +}*&1fP}),wB|b؝lނK^mZ⋯ӦL#%('HV.ǣR8䕱ٌk4b˖^Ah$S%Bj3 s9D-S6)\`жSb#0=<&~[s_\M0zIB( >STd91cbҀߎCG +3.'ŗI "7m +x`K %#j#G2Sh/H6z߀4@N.pK2} +$&~9twdxIW_5$(wK/ S%@o4 +0S p8 ?#o=d#h2ad(zGCOf0#m)R4 ~i96ӨUglM- ?r5~YG + M̧4+]n&db́6ѱ315-)@L)0Owœi*)) f ELTTaƶ*$? #RJDnm![c-a(k86 +D Q#~h i +v%Jf@L0WtcȦҌs3J! K]9#bڼO&14$*Ȫn!< LAqI +II/S@vM6иo^$$ +.&KjtZF8J +0O>bi۸؀l`a0 f3E'#i1l/ʑf7L@`:*(gu)f-Gxt"qPV1ꝩȼ/[OTtb(:([aYZ`STQ1a 2>"--D Qyc++K(bv(q%b֑G#KB*q7yݜ'.%JA~p%@oRP?a9·BuY\ _G(:TV݊"6H!j k&?]~SZe\ +D.(Oze(p\$7^s2/hʐ'n#))%dQ +ϖQ7>Q .B)16dYi{WOD%^ͱB7d(z ?q JA9MB*TZJKi +S[ Hri, Χp9jN|eAOjp%N!¹BC},2, + ;{"1%UM+\`KVHP) l8U'uY֧>E??#K1/?<)SZ7o'X=J_{tC '`o{r xO~Thսt }Bu< p%e # +ySwC_.n}4K ++&.B8}տ*De[ 2-@u(?sډ0W\\@?ZQmQ HK1|*SH'Xؗq!U߼Cnt0I Fa%xO$ N BK?m"DWẙҁ9}40_gO'h +|Vc{S/^ɪ.D `RkҬ9_s(FJ ,zS?$_O!G}Wꝰ_O8!8ķF9} +p??)?erTٶyOl,j': aol!|8=qe#WW/+|^ +`FFRyTf|$ Whdt5 +7tWAlS;R"zs +^$8WdzVQLUƦf[lD:`jӀ܆ ~*Ჵ +ʔ, g@Њ]sNo;/QW?A鬱+9e ?4;8 QՔ + "E" + U2 +-_NBEf5V3$,n +/G% +|bT@ 6z8p.u\Df6,N%.Yd[Hİ"_=^3d炫V]8 O9NdYh5 +S͐cd rǮ@^k=y<:-F=cN;eKݬE헃L(-A +M>;]R:&@hEAH,ΘSL5`LNdPmp&5zlBY |5Gp~u k\rzI, +Ӹ +gA3p?gpK^Op~Z|#(NECTu;>=ǤZ~rZq_\"v??oE ++wZhi?|A |CFwˡ]w-pZ 0@?Boi4@] +맂0m#u*# EW-(|oDu]F H:Q&Rl˫5Qk8C}:%G`rs +I7m`!_.*XI>CbJR_ (Em{8]iJ=E 7Ba`Ryu]&h(u5}Z |/V rioƊ駆үBũ@)<'c`g ^~,Q^ S[ڭh?Z*Ppp&$$`PF1b:z#R *C+`k;с4] ;z>WH OoWѯ,54N4i23KA;)7&Fupz+>fnχ P=|s) 7=IvՁQ+QA4ȱݎC* l. ).[\E^x>!ףo#+z?GH[iƟMqnx?`UW7ꃗU{:QPߠ[W?﹕L&6J-Tp .@gxdfy t|A#̂BsshV%Iy1ls3Q)rӰ'+VZ8?w^Jf_q_Zgjbq] #V +^%¬E⭶/޾:X65,}+fQ!'KFS`^1*FV)@0 +6,hX[$I +U6Ѹx _y +ߎ`JVG, +BBDN9[hJkRٲ ve77h^ބS"vimbd*/?(#.sCDZxv%JEPkha1FFһԽͰ + ȩ~e3](Vs7d.A!rԐq8jA#I±.HG`'pe\2 g* +?U,D +@ν D)Ʒ_˲{5: += +Yqh}Fjx̢ߔGȓ[YaFB@}+!# +cxBjO +-4d y, p:ZFjo'ؿsmoFEUnm`2̿8H- +OcB99᧟~8[W +? .tʵT+8]þ8%h*E~*wRԪučPYGt[ٮ8jN+WK +n䲴MMmܥ_(;;H{ WLl5jEr*c fd:ZᔩW mMh1*P/lD݂j  +-l&_ 6l{ +/t Y[=oH;Xj5 + HlI< zWbvc|DVϚBOf,39eŸ''T!P(b=px%AJ䊹Cjlj97FH+"(ߋFVp-[q0s Gqe a%iPal +N!KǛ~uE_/V{ +WH +r12 +:Z(3ZL!z-T_&^LcPf-uz@n(8J8"Ya#݆A0%è?S + FS0i +0Z +2yZ-1&H=5@8{}>7!&O綔]2 wuKz6!J)z +%U>!ɿ~I5_.Dqo`'4),| [-$,\X#Iy#Om? /!)&y+, `!ȱ4WEx +іfPT֪-*`DLjR&)`(+ *U=Hj2ZS`GHyVP/! +WbkmY Sյ)`UNYS%N_p0y +OU+^\F.h-f +Md%Tq) +jOA>B)ua:zb|Ӕr +#ݜGH]D~{._Qc?yO϶#:^}!y0wwٲܓҿ{:(C RJuAd +$Z +1We$ +NF`/O/1"6rlBoN|HTgU}TMFg2e +urM1Bw +41b<)P)2Q:a ;QŽhvg=2ɲ'MiMc^t F/?P-a u7Bkk4,+ ^E GayXV+a+ +hT$= +d%ѧ37 DjZ!C C ί +*tWd(pS77WKǾZy] +9v|fJo#xkuF? + DBb;`UG̩r7'[cޟߝߙWwݢ?gƟS3ҿ+ܖV\ UdGrU?|M# +*SU>nL~̓ +J(&klj +)\L jM x7 +G '7Og)[Q'%: [wa"nYWGGF +A}’4_]`/q S>Qj{=WhH5.fb Hj'z)7[X@_)]bo?\-{ +wSԭ mJYܿ +>az3zMFȁXr/%pN@@}B&ZaB><PL&;!@+2P ,G翌2/Ub&\:b ++U)^E +VYx|[;M0i[ 'xbQ0<"G+Ch +P6ts6Ck㣒ӈdEZ%zPގ@i3➇G7?@jf;j@%IbQ2U(f%zx()JYBKJk?w`(- +WmT +7zPFޕ9=v%-@@ f| +z8ja c0yldRqlo8_2?>9B4545XC\$DJI=fjrHUXFA`Ƴ]N oS!30{$G:\L!Rj }jeaaXEǢrD`G5 #I%) 0%-A~C |/$h?A2g+RLjVV + \jנ=~K'BQaɨTG21Lq`O+M5D-8 vBS$nat>">J-k%B7UrSv7w΃.Vt녷S|na4 #*]ot]H> FBWsF}~.a4Ee14*@xQKCPB@W0['3yK#SǸķ,#o hHIh0_tׁ1S8q |(71γָ4idsH2|,@V@fL2WЫ!-Dc@]02N~/x`CK B~ rujw9 MX>?i'6w +V8\{8ȣ)V14{h5IK\5 G,m0X n~JCc( +qJB sxc7 +=Y( 8gڼ[^ۊg<ZxeiJ_@άv['zA ODs/$h ҵF +o}KNF#h:qG>HLaPxŠa@Q*j0P++_ogղ,و@Zm{}Kp8Yت +(thA[Zix!qBP2)[`A!lij>v2h k `uu:"KSQX&xC$[N$1kC +I)p<~8)3CQi{"4=RhyKOj.BN *g1S"Hhx6(<0#nY=,5@s6GX?'ZIYBA]mO;%30~;T#6>l%q{ !}|xꌖg2g-ϣh!-6BKS*Եk#kS$A%x_a?tV TT:HBσڬoM)K}hTeMZ]ho'k +i8m'NrR>kBM!/9ѢV! `d8@>N w' ;n ∿g18)˨3(^:?r^k0 mgCi\TR%@*XdM@?Ίntz\G38/P' +pԔ}2=BhTqT +B.je B?O70C*dS !:2?,w'9D _yW0+RV9WMmli62k>+ +I +K +p$ `8ot2|a7Bf9 \wƛC?/ 8"8?7vHz/cնFTW"pC_X@w__54>!XI!Z9Τe.,W{>;N%4cK@n"yUvo CAg:`ge +h'z`iE|P?*`߭;e->5AHA^^ܕO)-w_qE{<%KD_Wʀ]=,XH`Ɵ3<'N} Shyh @GQ4ӇDtwn "2I2c )4@tcʽ5֤[ am$J̱K2c4n`nci Q.XA.Kyu_ n'Çv_E Upg wFZpPB +vXMAȩ[ @&MFOL&RVtR1qE +P ˞ EsoMRP LsPŷ\.#b\^6"s֭䪡$,2Xa2b6Õ$jg"BYxS8\(7R`7L2;:"EKYՒoxӵfAլJtL+a5R_Ҁ`AJ0 'U:H*l@]&hl7i +d&w2 - +.{B&op [Py-WMݧ۝*R%7LBX͆ֈ=/jl/jSUM'S*C0é+ +H4Bc2:B&l=b)d+. J=#E`] _ +9 k"kKK +paE|PjJ?[IAIڈ-we?ԏz z?(w𯗥+{<|E. +:m|;pKaRiHШއ!JIPC` NƐfb9 +Dǔ7+'uOrBQdV' p` ZTvQ+8w@n^qZc! ;Pc4"iN8OL>Ԥ)F=k & g*& 5O3_~mjvB:OhTN\UHFt54{X j\Y\1 _ڡU2ȬIJX}̣aJ+ kCBkLʀP,Iu UVb)_)! +4P =@/&.[ā$Iu[祇ڬSr#)/ @7R8.] WFT")IHZGrv2yu[MdWgE!'o!|d(J?}I_ + f4 +Udm'TX{#v*p MJ o* +P?^`Kʅ٦;Ӽ2SإcYN7E4eq66›to2v{1-+[h-6옴߯u|^ +i9iGa#LCqK +wKٽ]s K_lE{z +5w:i3h#T@ +.^̎cעFJaFzD)5vh{O)πQX烣ѷbtZf1lҵE./fM:=|C'\!6j j:0I\$frew&"Q<,82 v:¡wY;i}=P,r;=ϕrğ3z +c{{0ʮDÂ&(ĘA=EJ,(B'VոF[JRޡ0*NI;*Odfx?A&1)e# +P 9WFm*7N,yʓ.<3AT1h@6XPRXDRlbXldjl )%88u4r\[xml1톝B8d742}揀a9U 1x~B - heQ + @ojӈ],*0=gd6EPȶ~&W:KL +| AL%}y`kqG1]|c}p j!(" 3!!#ay#/5hoV-W GY.CwqTx_>Lx +{ˢx\TQ0.$ͨSCF +# +(G_w*KSݿg9 +Z +ӥ,&ƍsŶE/5Jw1]rZ[ (sCMF _ųj0/LzO1b"J^PPjʙt5&S6 + xڑ앉e/ +?ǥ=xj39O )B +'ȄO>Iǘ5MRVrc_'A$OU(@Ϋ1q$&FqbÕ+#ӗմU1`#*)gkۜѺ"yAeBB[xB`M(oowNsW1|o,W;k_J媀%:l`Neb h/{0x8< 6%K Z,rɡGќzՃ ׄv:X >m%rT &vHfb)M#1?\'3v"9!3S7C0:BY:Fc1A!r'C9:ƂHj1;P\.)Tj{C +@/|8 O%/=9i\ (止Vp%[^E<}s,h +_/ZCcm7> +~pҡT V_>'}[#Xԃh_\ +L GFDџoH8xawTa̻Aʢ->)we %38.U + 4O_җ[b +t쫿:Q~Ň}r =h!Gōd؄:HX@ϗt*a>v@?W ]Xj1Sh(D{T"oiS۴ЇcIS0O +xK̤P,=m_bՁeÂZ BcAJ#pJ7&@L켑 mb3Pʁ[&Po80s{0)! ^+0džŰd1 +ֵ\MZrW*̅F@?,X41濺>KNɚx|X-X(?\ -mPz֛bEX-:VQ `I@ yos? +L)z!L I#h:|6˩p,ifs'*2C'dnA@Zd +lq]q@ٓod:(fTF$$|ZkФZ`^cE'm,`c|(EZk3|lٹ% 1EIUV3o3N+B]{o˺krsP0K Xbd#M+3V ^ؒ%Z98  ?]Oɀu@/?~?/akp+jaS=3cRM%@pt݋‰ha>* ƣWs]@2g)X[Bu;`?h9ǘɮ,,4y^ZSiaU214jDT.Em[T|^z]lH#IF>BƈAT?azS~?~'^"-\"?.p  +w?f#38ԛ~*[ #\?BmnM#,@̇v4O_җWPFOبh]#FLDdo +S +Զ?\sRwhB!11MTRN@XK> + S:e˒[PRNj;qm̱@LjJ}GPDL$ی6`a\C_s,%#?|4;1?E<)[b)P3bZ%! +ORL)2PUp~bvIlm-CIYDěEOc 2*Sqq3Cf_fu!130b6,hn!8&Nɴxi0#W d\f*r2u;t4إQ_wZO(+4fԘi/$ h#7T?iϊ'_f*Z`)֣,wsRϱe뷒Z܀WIJYS?xvkZ/.!'֙,9sڕhiabbZj7| w<l/˃<=?Bm⁛& lsEN/867-e"^ay+s? Kg?$L)|RŽ/oOv/3d]mRUh#_ Id'c{.y3RD_3M@)qw":la8ח!=.;"i?~?~xG&8xS~e]iZF Vģf8 tT!q.͐D`L!,AZ' VITcWuDh$W'E +YCDhteUQ&? 9T +R_Q =nA:= 1XU !vIb CL+4\??Sb 9*۝K< +:k2IG?1(>muYB0*`Y8;2R]hJד ]z{ @3 .Y@V. +/l>ӐP൲<|_D ?"/q_5> 4O_җe?z[Yari +G//yԟ$GK,4i_Xi'$-f<7ܞ×m[vGIZ +=t5W[ + +UѯAW +Ce'n35 F +HaԎj>Ϸ |_cLH +.0s qɏgFh +@Yjb{h;vr17G̞i<̘IubAmHĝ0D&]q}R滤: +?;3q@OH>Z8=>5^l^ڡ8m"=8␒#xOW\RRt xk/, +˺0XY?FibIFϗGq~ #WNWqǞG%{k?轎JlDQC A=`atCB{ST6(|% +8H  +QQ־ndPx' Vp,eI=}J#Z0sڈ^#@u + &?{*ҴF +%Q"X +o BP(ر7M5fץiL7`Dfl4شG/=5.A؟E<uE<~܆=Y&}xI||Kf1kY$v}lГ@ bӞ:W2)G0K,Xz>鯗ARPv`r|~=6'7JN_n.OuF6fL[+c-9XĉRbIv4&S)3ύ@ѯ7 ̈́C0V"!0 Šl}&-AlFJ%}y +&p8Ve,yB'p7PG8J?|*J&*o FupRCpßHU; +sA=(v-1Օ|mL9 0{g< Z9sFRK-H(g$I`{gZjuV귫P콟YL$X(.;-d9$7L} yځ5@RjR20H>+ 34g+~w%/Q J%\},,O}f῟ٞlxU">j ^hH1;45o75>W7X"7.h0z~\[vV{ם~雬 +Jl[4 %w-%xܹ'}qO/H_g;̈́TNw?": i'X^=aTy-O5~$b_h"xپMajiO%fDk:O:\CNW< +)5V s7 t3ns6^18b]۝S%1^*Ow}پWuQLV ҚtrN=% ݺO826i=?![2nQx_ih'@+RV +~xP4 8'ςi|eK)YÚ4]zowRi]eZ:P\5K9Fױ.4> [0o,F1EnF[\[M$XY )*X|zN_Zi,R5ԎeAnKmG'C~U >/`_W#px o"{Ӟl"Y/33=i?5lb>% +_(2ҁ._lj40&|`ZT-ؽ0rXJӞx lJ +qDh]~Ȣ"hvP3'EP]; + )BJPXN:S[Tܩ Ȣdh*8tjCi=mc7:%=8>b NRO{h-ULfMQ>8Q8FhF oPvF +5!kAt\zc3g[P{}S&'f̓$r7=w珞JNRngJ{vNw tW"MUOu:Mf?( JyӇ'6N0pa;?N?={W +lpb#N{o3c9 zV>S>cѺJwOgJ +^:`d1d/S0OvZmŕ\2]+ +62^oK@o4# V|r ?N6_= yN6fQg{/O&SH4}-E i@Z2ǴH54Kv;wB fZu=u{@Ub@ +eUNJU.p', +R"ؔe1fm "L,`H,3AxQ:ch<"au#J' 1@>xنD, + `Oҩ#0\r scNDҴ fP ~ottPZ( ڣdXk+I.W=:q&O +ChN;Q2I %)nPJ]*" ) +Чf8tyԮrɲ'~( \@$m7)DFޮ#e#Z|Lxa +- K'E$԰N:%!Ԩ +5F]^#-N[[ dж\o|CJj~B2O̢5* [Tl ٕ#=jH)yѝ9_ߴ_Cn1|oG G(=Wa/%p;Xhn ֨x˚auwl=}JPz)' U!U`1ߩmJaaL:'~-;̒<*Lz]ڹ9xq?x8JEA@~)_p %|4\CA Oa{> @!Ϋ߷TP  + DskX|]n%imۭ᜻ sI$Z72m +diG ӟPJI?maOAߙSVMW5~}]do7dsj.M] w<:ɞUd~Cg\Y/e%vΏvJ3 ?~AqCZYq@5L@vˬoJXZ_m6"4 Sz@m?]*Ӿ*X!1T:yAf̑GQ~pG>Q-i?b q +c2J9_#Њn0H+$w#ȇKf1Qٚ*$&U@WUUyb0tgrr1zQchxI6A4P!afA +t- c4t}x mX=U~|_CvóGsHw :"aۙgf?}Ӟj +^'w-=q9D{?&~JЫH%)YH|Ŀ' >f = N)ԙ?g{ۊ#5S"?GK $KJ"n3PFNQ. e6W[v(( +W~Y8q5Px?}"}J kc +Y +T,Bdn_|> +AK7~Lxȼe W3r"'cYʷZ$Ij`Qkbzc^b=KAe&mnVg80YǞ#i֦ +k?'lS`V]kx{N~[1]J̘(kL9ӂ2_XxQ_zf3XW)4ONxz vN_tjKQd.O=3eXHr`/|k8c8s^;/fSeC:Pu).(^<- +ioEzzJ+"^ሠko3lI2<(3w Ͽ-l\oE7#lk?O(x1(ySrByGs +vb<5gFOB_&uq2RVk`PoYtL&$Gwf fh'FOZ K7mY  +lRI4ǘf2`O\o=[ɷQm?/WT +/ ʻ춸w(4Uqd%~progѝfyVN +0 +k>Ppb[㕜"*-Â~ +iPXuq v`W$ʁϗCqx +]C“{gxvcȢBY̴Fo\*ѡU(+ZqBS/)K9b=-Ya/D$;~ 7 +p0tj'pn`LmlcuQ𞍞}e}R>Ŷꘪ3V5\!~e' Ζ#*̟oHx~sx`J!5Vg~.K*zFΫ Ἕe:^;?7Y߸$ ++b$Y  +肙$LIm +/J9GoNv)`OiBAhBv,IX nūۉ͒c1t +,f& +?B +v|-riNQJaUҴOu%mٴkN'(Bg5G9NOd 0cKh᧺\=~/ ?g0+ꈀx*wz_( ; w +>E !_` >zQ@`:wq毤NRLJ)f +]k&3W&;BU QD O__in]f,WwYlu+"yJ7dン'$"x-'tW@ݝ\2|(w\u fxgG[b+TQP j0@|@* FzAS!6ο׌4c`ѓJ?zT+KB窎'HbqOaOhϪL~jUS vH/1|Uʐ_/?c7 +{4;{e8eu Sv +Ϥb<ƒlCv{INZWiZlq*[>.7+ib׌5CuSgWrinul׋Q I +X[EPU,@1' e|Yz `8]lr{]ʝ"pyr:g{X( z~v_44~qPt~d2R kX.OY5_G/Zl{o e:_ IWxٞvz^P; ߉mmvDBe(Cqcս? G6-ސ sޗ;AUG3(@Z?@~b zw>!d0i>{poY; ^=(x ;Ohw{5˶ +X@Ku{v-Iz@vF]dC(krԻtE!E1y)8N2M>g߫, x01,5OA72zLJ!z + 7+جr y +U@,@B\a}T5^Ҕ.0uo6:]v7Jp7}Ѥ4DYI-Ғv*:+/bوrBj+=혘СbzM`g]ªG.O^έI +iZ/ Տ#aQc8D(`=()jt <&=$&6kt{ B{!_Ӓ_&P E +.U8%TC0cDi3A Qrg/M~ߴ/g +%:Qc](Qg*v>Pp=1w-%57Pþ\sEt' -f+߮fxuQT[|F-^O٘Kn[h5H1N09DIŠ#(/ .`C5;>ppQGh4D`]MΥ2ZBEvjAEvxJ8[f}{auZkŒ uW%lw8=`!5y^edoBT b1Ip(zW#<Y4EnJ"R^L qW`|nYqߵJL~O79gm=ڪ۝ 7h +Г{*@V#>z?R`//ǒWOqϴm7[ ))V!c +!Y9 mkF /fcon!Jp䜞MAS0Y@ bAq`1K<( ʟx/H>p{=eNgm^ev-F(ZX?93y2gW ++o" uW93Z5BӇq bQ2[i@LV b{R vz>Rd/7]F@/γ& z=26@n؞y<~`.{dxW۾u)V+`m<D}bt +V[hDz^nD;TG6+&^% +tt׫zAUBliސwuDl7%rFЅ)?;ǵbV=%(>LI=~o$ߊ>x= %m?v%{~!5_K0"uqNvdumq86<to1mx"!6kASbDL@9Y ~*z|K1"&\<Ow +b3iqNYb + +F<=r?PJ +JzP_YxڿNA(#"eׅ+BaůDtm3/-xA@QaK.z5"SW@,v\" nG APy\W)z3"@Znk/eGV70py~)ձ|GOF!CO~bt,to .S]Y050Ȅ)hD|{A 5?~sC0s!> 3*[:/$# /ܮ̰<>oho `68:\ؠF0EYZz!#^ yg|2K0O # w,  rwW4XhW`[nhM aQ*:ޑs!q씍*̟%vҦ O՚'80&/=H)m݋;9qT +>~>A1 )Co3⩶"bq '?"aom +T +[vG6&ޥ{v.RYO}B-a'wdp(S^r%ԧ `=MC +>k};^e 3l>2^G fklj)v7K ;&.&%s- +U.OTv#`.3{TnQ X&𻛓;A +!=Ji#s +Q@Ƴ:, 'Lu_ogo6d+Hrf +&W {qG)?Q@~Qr U6V7bks'd(llϝF5E 牂SN,D+z2m4duU +m!Š݃i_,(`z쫌 LDXlPD+XXR۟r]R + I CWAȴ M^ÂkUTD +Fa&Y +q7W"@P`k G6e>nNN*BuAb)H@%=F +md3P/ԲOհ >uYjyNHIр |nȧIJ}qkO,\휥@Sm)`$$CpAx;ԯ,U7r9&Z&km|@C~!gVthWj(@<5(@ҫCb^h.Dq~&6$̘ ^g + j_W l)\tVfō@$V\Kr=ag$_oRi~/:)a8lg``bɹco$UV>FܣVڰVtEOECY3 ]Il/"{`Oa]d\ TXhbC@V0?0~Ylm+҃{$-2a6?ntF6uBB +6{ֺq:IqPzI[<}4;O̸~֫?6+J2< d>toYw ..yfE<HePLmhW N7T|HˁT/ N]{qХd@Q2;YyR%;!Ymx !J:;g _T ,j +T(Dh ԠU2 ]/=zJ +,L&І&2F ¿[-Y^gyYHDw]2-ή35ɟ'BT|)vlOɘ #YGzõfwGi3xRy'ڇHoIECI:@OU|lI3"Y]%D< 챬]6Rخ@~\}hS (|Q +@#t1&.{CYMt,03&"){mkT mǠ+5LA)_SԦ*"z/1'NU%@UpߤHۨLX 5PraT35^ȠbP (?Zi'o>Ȯ?^-0|E%"lƨ@uMhBk$""$T pZ,q0]2XL4 D l/>xjl6>Vk,fP Ճa/lE ďܳ/":귿 Ĥ3ADŽ"hӅG[U߮a9 +]ϒ}fW(Q α1P qy<մULe:Ñ߃. IC)`hxP%s ]Pݲch&nCLYz_tOkK +*}0O$,.|7mc +A +?LOHu]"@!.2t=MR_r"ˆD VoPi~`ëm"fQ/|5W$z_f(́.9ժ֙_.pt݊ޭXSo7ZNur&JJ(rJG @X@'L LV!~yI6M?J**F>&be=WG)I/l#xc8{7Wc!h]?ھ5>O 3-? +QSUmm0NOcJTG@D_b ?/Å #Oe $:RCYjz8jQ%U((qW3~Hœ&SnB32B@# 9z鈀ъ'z#xp Bg JXz\ I5q+Ö8L?8ҩie +î k%c]!CUxFHMA.jr#9s&ĝ/yㄨ~VK_ط}E#4IaXl3"WjokmUl6~N;chlړ& +#CH!STq9it +HٍΓyօ- +r;ke9Me𢡊>L ZLAZxVh֋JGG@p$ +&pco.lhAW,At~ÏY#_K}٨ Ϸӄ@3%9ؔ30[ԊwW)huCz9w38'TvC&@h"DW-/lU4R=csR +qy!liޘ27Yd`d@ +WPevu ūk@*aT)(Vf9/e{֢Z X[Qm"5usE߮33O0CkJ=݁< +_F oRzeJ\*]=[гIyP1wQu6P8G>ԧ\8W;ͣ<- +)]SҾ`^?uS.P,[Šids Urxw#g@˝"j +LA紓0: ]h3`.\UUjPX΁P%#e#̟j,O +~I"L+IX .b֥pŰLN6hxӑ."u(2 [[_9J]'5.h|!P;3()Ôvc"@K4X,]! +r## $ +0Z߀CPqf`xX ]1?mmaxpN1Vx}ꂄT$#5m f jCX*asۛUeu$5=g㺸ᤳMWyhKG ,O슄Z<W0l4PZjj$3w98u.bJ` Febw^-: oV&,S0?aP^@Xu_P*j1}m# +(.4̱)D]>OmX" o׎誗-}~e}P',O{x*ǜjS]@.u0L/1x® O.oG ͦ(CY/`'g& k/N0?}5/ ?<3@enU +amn') +[$n1li4)N + E+UU[`_Ȏ`0a^VJ$\2 r۟m=,S Ԃ*/=  ~37}V2=r=vd9~qWyud}gco +߶kln#7Л,9muA 9.O`\|/Z,u9 +(>< 6Ӝ^ZRf)URm[wv Pc3T +D#3x@ +-?Ku4\JDMbG˺m+,dFJ9ҋ#}>_/wA[`|3DIzCM4,trHFyVbäUif,z?@Z-4a$L{x퐩`׸$Uq[ybrT. &=j1~W SȶȵSM(pof.2oCW[ kV_ +g!dUvUeav*bW`V٩?lPE4S-*|vT"tJK||i5y E$d +=H± +@ò5>Xò ;&9Ŏkϋ Sx_ld1R;YX{^'K\ (m( ۚ9bOأÿOj1Qi$ y=l)IkH1C&,PaiSГG"lv;Si~w'^6QH?</*@7H()ǿ3@!N{Ǝ??Lt]>A YRCz՟CɃh:ơI9dz| o0N"Im>0ƆTѫbqGw@=5Ux}R8ǯi fYbM#UE{!_颞J:18@:~#U> _)>M]e} +.LBQ[߶hi2 + (O1= 5B/6l@= )O`;C# aO6H^Rc@0e@LzO3Ӽ+3jm],jZ]0I}?Ћ^rcY7 +5(+A)ȫ5JUe]/ (Z2|Ĩ|0#h,5TbwB&T̹@ +?`K s YowyO}Q/  +@@ޅ-D(bNwM1fo_=vy@(oUQcOC?)/|֤.$sޜNڙE~z$?~wie'F}y?*,v*\s]t,DY]n)$ vG*ҫHx[Ul2gU\RMG|5_iS@>"!p<t>ZO!=}z1 >LZ|ƫI_'CXx.Amaqyh/78NSSrц/ :VZ~¸_^棟J+l %"{'R8135 S(mgޛ[Y_yڴ8 *8"Fcҿۤm66MoFEAyDQDQqePYc^uCz=s{Z!9LYTg'mxRQA.% +ЩfU/1IgWh!pQ߉Տ-Ds +YFx~d#':P},~X +#9 +Gt E{uBD?"7h +̟/962B ΰz; +-o=U Ix + 1/JҀ{ +l+{-3ddW^Em!; 6bl(z%rs(yLehd+/o + ;߰}?? + +%Nl+v"7g~4)( IrhSms7M<T4)5hd +#v?7`-&l2^WgJ)m +Vs4`~[ N;ëI։3>nPa&=G>1bMc~?7~|~6n}hbo>>eg@`*2(o0s歲^7V` \SpM:զaJx\i/7RĦ'3|N)ʷJyr LR2*E +gl6/{L)VGe?=S6+Rb\F+Jcj^sw~r 7efPOGBW +IT|.}_{4 OYUFe@3yRaD|g~5u&H=3ζM]ӣ +@E{C ܫ< vX-S͊(]?K{ bsQ֧<۔#pkZk|wG5zc_C H^ez%@+㓋@3UNgS* $_G(R7`{|[4ؐ?F)}yAyYfRPFo +n,80$ + *U +O0ܙ=>W?9XMg4_w,EPᶹ¶ +P Ȑ/(!J'噞̯-ɈZrS! +PcD;.}qW Ŷ)h)aGqRU9VQ@k%M }v`O'  Oˎ ak3?` $h{>zpy6qbإR3wϓtx/7'7 Kg= + Q@sĂ +֫:^kdH 涅 + +6ѝ@wGA@ᘍ@X.ddחWV|@e0H:+Q[`ul"zzY8ZEi{ ԯ#UzOy/{0̅wvվ[p,I|e[/ twj]PV6ȺOQмOJ2:t/,pE}ltXd_P^Qe9 +k~&a/M= +87/y)zM(w5O_Xk#Q|Ce^qH'Dvt'"#-fTw"6Aw$s [IOF]'p4q®6 +v}p @7]a(| tΒ6W &b3,SbŎɧ +@1Bo9v @d)JU(s ʍ|9tVCbK$U0bS[GSٶG[?`ްpi[<>{SS|*RV!HC +?#Xy)/7R@ŊӖ?1- ?ns +V/R4Uuab`xaYViz_eͬWI N L +FLc=v8G3Xy.5 ڢ# )'ujj:?cu Z +#N~Cxf~݈Ut~B'?+ sg0Wc/g`XQesv .!&GhC 'unwo4Z_X#z  Z1Z"n%C)>I^1b=:3DA2ot4<.,hOzdfnrid;Ytd/g7,Ba;` ehm1[v%E*; 4Fw&c[tȷhEXGpw>>Z`W^*/+R x  BnU2q +jaS*:F-I(}L!c T.ϗ,„eW,]͑b*owy Jsyȑn6&Fvϡ+fJ">N*IۼBlH^+'tK3ojΏFwBA) tBz ;fm[l4~(]`d?nv2I-M<ti#2 +0a˅P9" We~ _ +WRN[-0@.@ +=D~>R]wk{֨/:O0T?$Z?pԡ`ŲA:_y/"CjEsi}H!Ӑ+.t`˶ѓvR3 ն +X#"fW;  ҡP[ѭA977u珊!l^~׸XCWpȀ+WTP9û%MsYhA^eGSU-g_UYNrIS:Uaֵp8ǻK^3ն +4|uv(ML󚈦jɄ]?'"3VipD;Ob[J1 ui~"fs:Pd(  B@Ԁu5 F$A}eX>q9]Nt/!S|`dq w<$:'|`#Su{D*γ^rS//wD;Q[[_zk vޚo2tzbCK Zna/fQLjs"_^ȍӽNdỲߔ(w +\-c?ˎwV2矢"h v8L2Op_୙LT: +KD:_D% +?Y#te$O*[BLʉBEl]dJ3}YyQȟ0" ["r^18ڂ\;q6w'kԕI&9ufo(5]tt +`X(zmkh?E&jpLH*i{Ԛ~)=6@jF +a'4.#(+ ,N +T35ēw +UvՂk+9\D+7OY1 &pSGk'7 +H>*@ +Ǚ`d>a|CF.^1~M+ "n: c#א&<?DX/^s&CQZ +&~+ ^/cɆM"Zf@b :U2Ό@3H>@ "!g;cS\%ԑec?rE;=9es)O99,S5v!`z}ܘT ?,}nqhU0ƯMT(F< ďZH!@X#$a]}t)wY嬲n-U8GwxDbܠD_ww3ܺk %'^vO<+HD3U`=;ϞY;CKP|mךbҁg~9?@i_jʩTl)ap} xAUkZ\DعU?[=x[ %L4m6UdMH~8A|CHd([;wуi.]'1L'y#PbQwRK>\r 50>N dx|_ul#\Mw=aylmqxn:^w +֠u.-;_n~\PUpߖC+kiHzG m&--"ѭK +J.J6%:b\r%+'$i +B 8B!.;#SB&x!&}.B1_枮Ll-'{ +>> +>[ V@tF + +~ATY@2_*<*R LS0|Q_0-ΫJ[} +sP,7M ![͌Uɹ$e  +Lw$F.'Ou]8;縤m8?#q#M dR^l-WQ, n26Dǂ /1@L¬jA &P*p&C2w}܃9Pp!K77jswX|DvFdg}nS::">A= |ԛzO8hfVQZs_gry95<\c +Sweg?|&,#^WW?Đ)5 LO:Y}Htdž4?#FjtM:z{M De@gwj? LK仑gBK@:nӇʖi/0+Klƒ?4TU<LϿ_89<}vc/'P&z!jJnfI '1" +$ [ɊP +s\PZ(WXw\n1p0ZbyBez?=H zson_+- <>sB '=[uO>㴫8Y;*E&}@AoQTao֘F&Z]r +M@诹"|˗F%ϸd#_`rJ#QFtk,h=TBϲP<vtKG8  ?~> +"2Xb7Ձ +\d4ML??t&VVĚ`@#2#Wސ|)W) 9tIɠK1~º\T^?BcLlnJ=lq. +qbPvD<@UeaZMb/$vkߘ'""jj̈(UbFVbi1QBNR.V,{UcQąl. +C}hwʠ]TC(G:#wD˕SԞϱgjSPlj& =t}|?H +g\Ќ؟Z3A +dN˻-N-Mw=qgG"X/L7_h /PtoD,\{4 s&љm I)? +:VwG؇<< _g֠a* +w/;oGۤ"Ya >U ~(\de˲g; +Zܺ>/" r`"y[MZo)zWuP!txL石=!OJ`pIP% Oĸgp,]M,"k{ >> +M>sy,&Ii~i Wg}SHWa:h +hK_CG7;?[kՌTOAf3PzT+e* * +ԴIrӁ^* %`$5=CH{!>U:+GDH, 4.n)󂣣to(hͰh[qkgD +FNQ;$EA20DčE3a.j'hrvq]|.~CCfG@TJ~zmsM@ t1|6v4~/E7p +J9 WOމCyU)q@o Z\zΑ\~ܤAD/3αOI; &KSJX;,q\qð{B rj0~Sl6U?O=#W!! c k5 зRre./mj3`t=k"@#A0rJcY0pf,\!6!s60=|P-h;ο#҇B3 QWOz.i }ퟑ1-۽S(PD]9Ta{5qje¿g"@1DgϏV9 +*:&]}t~ԗZsyibLa?\/yoZ/h`@ i<>+-wZ Ң_+R5YWyWмyG  J+{ڵj,_ +B#e5Gb^m|,4ɜbK/gu;= pdyz4f=yJP? W=b` 0S$*- +|bɰG fۍ0d/CdoNPs&l? P-8U|Á ^?L' JB8mr'nP uB@S]cq"Q3: nW ?)T +>Edԇh-pz%9JcD RVPC9:py`ɚBP`&˙$\ѐxBȹL)=l5$")|2$6>ƺU܆HUQX )sDTBF@Wo6 ukI"U^`1{OpK.|~v%&kN;áLѴKM=59'F!+&F QeUލlsn!?k3P@:W31TIm*|KD=zL|?,(>!i||w'ZVDtƃ9v^б_ \u1&`0]V :֠Ó_a`rq /T"7 ;|iǿL{ vzM,f;WԀYRoa]p'%Q 7 +".nAX.'CV9SrOdx/E;|>` ߘn?[cO#;ێykCϘ|XWٕ4‘ +l +sK(''5!2DW&k¹U^m䟵Lopu~-{MUfNgo3 g|r V,Qs#||,8NLMT%hO1,bdDk')^{G㛸vht,?$4$_ޤ |ppz AcWjXpU OI\ l&ז{gGhB4 H;BkF +צm 5>]d ml;`6 ֊orx?7J-.** -U.3# % PXdۥhľf/q ИP8^wN5  Դ/|_;|w('$}u Kuļ.[֙*_gK*# +2H3-CbR\PrX *<%#?7xTP?X7 z%BkH +0N02X1AGFl^:"c ty5Z)嬰0.}4guJڒD'N#_iiXh?)n +fƟzư +NDt!niU +nc#=}غ/Q&PX(Xc9A u _7h)%/?< >C?o"lv +b\//25-eE-U嗜$ƈ U~a``;hBi0qI =Ea`Dѓ]"xK,|Ei礶3<̞?~d4YIDZ\[yp!T/7:\x9dQE°cJe14t2@ +ϴ"O?_-@ 3wk(ou6s>MYiK"T!,}"L)#h3ahSP7)7Kiƹ忡a{V]OœDgkpN0-ZpJm/#I:sqI#&SRP&3SMpg"nRDͧ'e@rXmFf1Ɓf<\~,툷,3"^c('eNBkm+lc>0\Lϟ$ZoHo +bI(r2KuõvoUjBgS3_=}vx %NG4f. +e_'WFs_Ⰳ2%,r2Ͷ| 4&-qDg1-Q$Of3Dy0LJߪٿTR$Xl" K +˥#n, +rL@\ _>ϡ]A[v1~SP1K)`zy7ۜ zO&? xMM +o/L%B, k y4ヶ24\t$̓/Trs!nܞB=XHOg-MFK6[ -C\luqe6i8oMӠaM%`w{nP^lڒ+V,w%Y±Bbv{ 1q ++7?\=?g7\zhS +8'OJ#]W.J_b8 Ħ:);_V1%VZ:дg?]^"|(T2Vxx^A%r+䮐h-VpAo&an9^ߩIq.f_oofc+%bKDz4Rsq텊x >ˏR-EɈuV +׈~wTb1d=@$EzӟW-Tl|H/?Bzlk# 2Ïs +Px"bÅs0@ \ JleՄQX}?齨 +.pEO}خ0v)G7_[:󧬴_Axd9k(Zj4SX " 7+PI0 ?S=`M{U73#]-_a,1SV~h^=|ZѢĮ,\`m:K(h93LA:b,'T'+!?$\-Wd P:"K̨j`i `1^őA̜%+C-6!Kaͪ=nd!S o ד]E)bz(|ʽyh7bF/_E-&ye af 5&5]k3s"/g.68MHro~RnGy.{lk>1pY ~5Ub=WӝfA#/t$AW 9?߈I^M냬\w/݊v]{u;YdKNE'4Ρ5IE4S18Ooj(צZfU&3{Lr 1:䓷tAn@m|0^F,/( cя]?kUoC%0ґ춑\ϘנeTv%[t¼"| +?1ONد"WGu=4hRE\o;g[oK61KmYLC6xA#]b02λD!pMmr٬&oZVz +f@wξr̳Ks02K0"gX<6DFPӞ'~G} +vٍe++~"h):}`AD3R8Q,wg#8(.n*/ S] + Z +>37GYr/2ʶ?{=O'aV ?> F1sk (7{J:^w?1uĢp_ .S#ӎuĻyGUE_h?kި`:6oz8SQS _c +R Et,#=392 u~ë -Y]E?]C+̟.z +OB +%W-E~DA{'W܃gTVx1uҁ+0Wo ح2 w?>lOqTOl1"j +ž ibY7Ws2M  Y% 8@2yƐɢH6~l"kEƸުEh\_:_0 +vpYSKW{1H_oJu#'hoLĘel$\rKLN_"傑pLd.^Iȿc )mMs;':81xV{7Ri*A3|i1 +`Eq^&+},e} (YTe9(n1`JormF$-@nD߃zI}3N{,^U > ?sd31sO?3P3>ǹS@$l,jʲQO=NHof1X$Տ2Ίޖ',]B~,"Ɗ|Q̍%LuH +1XȐEV׸wtZreԙk%T/d4gEs3wI%S.*n޸Yb3ћ#0?љr +7ElhYN_oy~$_ 6Fq2_&t]'+i~ۍp +Ф;+ +#X1S h 7eA^0ޅ +,P}/zP)9GK) +.Y E2L +є5(YFY^C(ɿ6ʜFf2k<$b +82uxUfڵESuU!PE&aT/yYVX2|tn՗Bm0 ~o(!RU !X@[* Viq)orhgo!;0FFG +1+Ca/ 1R[xFd +{}qȡTBkeՑSKA-KnD\}G)]FwQiN^:#gix?nhk-Wam* +#nIC: +ϴD07J9egšK2UҰ7z@ +|w7eOZ",ZdzhIU.zz{tn̹둝N\w ;! Є>C!#_6ң>":Rz7XJ/I=* 4^$7700RE'KmhӟּU:hO`(afN0yBޓENOg +wZJih鵐fC~g.Y<(6R.x҉՟^G!p7}Uy*ԝ U&BUh_Brn%?k^)lqq8g煕G%qZP*7d:=ȁg*١4P jʮr!@7Ws/?&y,w +S.d5-G`Lacf|n~`r;ia䉔 l .ꋫ`^Yb@# TvH]Us0vppw S{6xoQtҳFX?GWkB3T_*52_U_%XF_bDEFτP5# +@ $ n:̕Hʮ[ػ h ӒT|( ߲g8 +nr<6n )~P'0SлA"A0v쉎co=<''J,} Ù~1-;s$_ʰP?EDoOMgЪQ#(^^=cv';hthPnG#|S^5*N +"[}Mo1 + p'XͿ]Z{l*?7Np7!b@2mG{,򡷆|?@i' @s]s |vwsi>{?3z#&v`y(g:ٗaI3~< )V-? Uď؇ ԝqA+x)4Wcuz 1^`8hon|;O ] +Ǟ0b8$~u8 :~6bW$ +gc3ѹ=%i9|gMP\OV{3لp$ï& @[m4SZVͶ??lof}ZG&0-GPH>=?[6 GhܿY{"B5‰HjkEolѐda_}LԝTxљ,|pf M??[Y8 E\NdQ-[,,3-MS B#??[.++]\,15l1s@9ǶP!Z   7<ԇ#NV\lvU +ޥ5@E +,(W~;[Ew˩2| FOQ6dgOxg ()L:~#[9V.(zřU0deN=XJ7;ل]ӿ +{}QPig:8ms3V]bWn~>cu=!\EM\8J 絔^Ew ÿ.$(+vY,:^cB-03"|Qn[]1\(iwj@ϙOy /( mTؼlL9ޛ-~[ +Y# 3Ejt7s=?b+6s51xMF &TxXG6b 0 ȌoL̀OCdX)XLҀ!a#+YhlK(S + bZBpR4ELpxD%n !ueڊ 7q [BF!iK˷tK[!<鴟B:4I[rT +I~M$B]G! DB·j3ky=`yGz D?)ba ?Q ?蘽W;#t{Zh8]>a9۾ӗ?d +mP +K;Ie{մj' +Dnz<BrG +7q'f)TAB7 deL/<QHAxzHwi㄂KAT+, +w~hʝJ^ِ|wzjN"4EĂI} +ًIQ8ʐ3zoze6hJ +S@@^ш) +gTEi4_°ƗD?m$s^X, = eow2. +c:QDߛV3?1 0{ 'kJZ+|#OWץK´&!Ϟ< 0`L)ʂ(\ I$|`LA rhNA4:VųNt?Q3j%h"Sq./4hSA<6kX;}4SY ;Z +$*Wg,!"X~ +ZLP)Z|6q nDt}]عDiÂuŨ)Lv7[IF!]UJ +٩@h<T> +}Ḩ( +`'X *qQMAOzȼb-.I7 7igĕ|]y\9KHIc5ZVkb#{M٬<{# ++!r͇3  F +OZ)qfLMP9 k'-$y +lv;K 3֋UڽS(j¯nEiӁ'ae/cC)uzL&'A+<*w +)xh왷yϜWl|J}D?y'dEߝ=%$9 +91 (XC"S5VwL>Ld2oB +KQTqA/P{K +ݏoR( bcI)1aC}Vy4CWgIlDp +y81 ++B @v8R 0/-xxch +#l(dE1c +ȗ̿*tW7`9''cm<،g]o +@_ OQ@;@wM_ځP_zVVV[Ain"Ϡx Y<~ +]۽"r/&x8)(J +ƕ3c90%} +GL/:_&hYnl77&&9R-@x9G ɓ:P(@&EKψQ`Ll 7@}VQW]V 菹cf [dǿpǃk4{@ nD p7 oHf^x^؉e>u+0$ pxr*0<)v_AM}.˫8ͨ8ӄ9q?Ɋc6}feZ#{//tHp{̑uG@d?p [XGP +fay(ZNp2Vߏx+?J3D90{c f + ~(4{+K[ +5HBO}vY:o')%zlJ,Y-NO >P$+M,CG ]3@śs?h<+!يO>]fJc;O!m8`~vΠ ؎şE+ ?QoᷘAΤB&p  +RPT7a +)(FR poIgT\̂F P`FOcJhUtʊӹy$CcT P/Qy,;ft92/y@/UlO5AT+/A )= +gE*lp'3?w^P*]?/;WC4 |Y*WW׆`aK.P "ˌ= OwR+ h= 9PCi=kHkeH,Ơљ6ߨ ː4J:=,HH`ONײ'N=|~8iSQS?Б Btl/ +%RMP +SH z@( +(4 +h>{oݪst{tWWWWϽ pX:@gi +2nq:P + `OG '< +ؾ ؘM@L ϑ7b'q +K}d +@w$PTb~[$yS'ho8i.K.iI}ub'D^!I&.!<+.nr@AĆJxk^=>`N+ 3vD8\v8پDfM ڨtqf]&%pޱwT?'rlB@krj_7 &ZSy~ZPZ|pޞǫj: 8VP3g"*)ҍhv@k7VY#u  +]i>R ;gSG+ҙhcr\]ϊC!Gc,^F\Av@RSmn қђ|]Ĉ>t0oaVf,V +`!7 +䑿Wcwy}1W) +zaF@.7(~k +UFhGObYSia翭|Kf,~؟C+N*c+ +8BƵł[~{؛XruG|:@4cPfR-x/InULX:Oio9fj+P6\ZhAO?tH iliˠ'df*8"ofA +_uP?w>I +?Dy2W#&"~+{OpDp\MpGPh(k#  ZAզTk N[!N[%wF[FU`$^@A*ìKӹW˜#roT7WM lv] L]¿m(Unٔf@jjјX}x\e@>R!xѺNs߲!y/F`#O#Hu̜Ď4jMlR稛(1jhž +f(Nٓ+r?CɟVR +Ʈo i'I?[*ߓy92s/tC3- i5gWESg1T]]@@_#әvA = #qPe +MPtF $Hs gCA;B  +#iu"1m*aC +sHrVh ޿˷1#"~<%$p*(1;~mf}f0„%H!Fj"+.DJa:QKwQu"΋A +^؟&;n+[ 5'OV*4pP,Oo2"chnV+ ,#VgD֙衁VS-6..zv"G2]@U F P K48?jv!N; C$R5gm hLp9BF`X_O +Yz}paZLX]q<3PM8 +PK?s'; DUU^Y4~"Z}HQ,XVS^3H@ LՄ u *7`X?" n$#_)=RR:&t!ckQ)teֻ/?й52hb%`:g~G>I>NKT?j.ւ\R qus61JwX'ON6?:k7e[T/cb4'&!j 0#l˖2Ѧ|U =*ap)@ "xPTDHeX}k" -.,kӮg#Ȃ=i0:ig1;k\i!n@%ikaȚۺ[ϟYZ>(rL8L8/tgD=jMۛ1vľZGF+ 5PE +(o ]2kB4eU_ߥ2x;+է|9g֮iv7fh8c@*` +O + +Z 5 +(9ha@Sx4#jy@Գ |,(Zt_B2a; ` t֠0؀A`oﻯ<*9e j)3" +!LM0(Pt 1qUB(5Lӗkݸ}q%wlE{']I > Ʌ مZ\B ++4_USMAh /V#Х: *2G + +}xG +:ٻ9Ǝ Z5s4U,Uyf{I[YrߟY!@Z%'bs}7a7P_[4;r$ ")6#&CfZ7I[#!c(Y?G Cdoj֚Vڅp9k4G!m +Wc  q5`uJ +xg# +Nf, cPL /Z`N̨E_%cc޷@h5PxɿI>9b׶jIOJKhSȥVq'ZX!/EO3i\5$xVSTicO[u9%|yʍZc %oW^gDLC\ZWߴQ: ash.;3 r?F&Gz* +p}RDRodT^&eyUׁ/ٟ^4NOJ +r儴f08 cU*<Ïu* +π8W>'Zҭy~a6&Wgf aX$g |q&c6^z9W$POm)A +j!B:& G +d4:byu%@4'r.2QA6rꤗb?ĿцkfLvEhv/wU>Vq],E캃vlV? l> b#|HsXgebfI=V;bg`"Q0L j?wY^~ piC8\Ex6CtkC]54|(=J@ O'~ +'ej2Od-nlSMձ\G +t].} # + ~ +\#f +W0`MMfʀ+\n?#h_.Ggb*RLOguza^0$xXJxE)' z715R_Na{@?od9 +m+<+sN쩏Y䢯P$eh Re3jMxYD?pU* P;`?;" +63>`8,\='֔&%1Lȴ)%?3; +"cfqɜ74W (@߷ +86A̴#unifAt4p +˯-uؐ)@}ث5l9|"H0&Oْ`F)`3IUJ ûhZ]qy 6KOZ P^/ ~ qbKC@%") `%F` +!MfO1?*́OP&t#%3\.:,F'?EO_`L s(dLVOcPWI; 5_9s`^,*޳Pf,ؼ !-M'"ùOd7{60K o#@ȘIz +o'7[u3sK aD +J:5 ek\͏Ŝջ:UݜE^D}FI$!h}g=Trd>}$mrO-ؓU"o~q`g,l?És M9>7B }vk;z=^Wh(|/FJ=CU}nEal95G3&poQ\̞y +d`9/Qf՝ueJ +/(JpO3#crH(j2[RJLNt +8>_} ̳Z~j#o9N7 +xg. }\߄b^D>7N/>[? l(C/e.IZZ%cte Ec"·ENXnQMF +Dn^@^X묔t -t2Afbwcޓ"?oUt!H ZӞ,:2dٳ9 `g,LP/dJ?d=q =9 |YTok%>#5\4 MN,;H%yX9$yO +]tL/LΡ4ڤ +b'6^*xa02 S ¿j`U걇 (}/D +4%VSp + & }{x8@ , tH5) 88* S&yjOOEykdGGhۧA_PuG# +PMug'V%>Bs4J:] n5 r\.О<=?=g +FY_~5ʘzILp|F._ZV]v6Q즰@郁?P?U +H?FmlSZ>$Mzrb#o0v-T+3גDTyswCwP=g [Z b_LUUuM_t-OWft&7?$p>?7Պ#R@/)S*7 ,dWZ$|Έ Y_P*>6 +T3r7!;VI^ i-= >kS&'*Q`5{|#R65oي7/4J\1KQoa*3Z` +|ܡMiOi0G +{tKn297j/y +@E0}A!:?5Ʒr 9_ B"W3 +9O*VG5A_D}tֲwi9 +p$;lXB.ImP X]0(:8{0u(e3 ; . %lzÈ,# Yi!vu4z'|2ݖJq} +itjEE\@$+#=g|M`K .&:BY=gv9#  JM5)2JӸU( nB(1'E~ \t8|wNcD"N-V4;+yJ + ʮ-$TP+ەE +T{X:b6`Xbp[N @qpŇ1/֠T Zh/Ll9&gJql 9 h i`V_鹂fT `ԩR+i^ .Ӑ.DODT3 +jq"`W(P<ˆ#8Jg477pz-̡MdxCގ}~{P c$ +Sj:|j=g +` =2^[|-,{o{AURK/ؕ؀X[H_?UYg:qܐ?R*ܒ7 Ce?;q!bk,{]IGW|@$QbyF-G RSѕw:9OnU;Jm[+ N/`ѽ/ +8 +o{xl9fL6AmƏ7iTAm +^2rr/a[P +?te-,Z+1NG ԏCFƌʃ6bߚi|_!,C-@pU 5J +j"d1 &PX0l˩0%94I +}N3 v9~.,S9Q?̌odRnƘoFv{m^Et#3i\)`bJr빟^2`xprCu@h6 Ზ8AЕt}ˍP//1/E=vyr+JE0&w;~H'(yiG@%HԀN I)gqR_h0F+hz9&2dHgFP?Rtġ0%h +._Pp%]^I͚| +]MƤC% +b|)chcyR?Oƚ`< E2kt@̥N)ꫳ>Ŵb :E }5>J ֠j'YT,h $1`;>խbԵ +"y }]F3RG,=_ʅH +cɡB%K +QzL`NܭfŸ|c"P{1S*|[ѭ*?2P?3NK"t +Vu(>A]_K !P\P ?$(>Pko#(&3U"Y?s hQXt~Ase"`i1c.G)@U p(V`#t;%ߢĞ( +p+ KU;(*>DUM |@XMճ[PrӰ)b +ۃDACu +ybwhn][CO870&K ԯ{?ȾC_~w +N,8\w[M777K'9*]:e%?ȊW1&cϫ84!Y E\?g婿BB?FQeu bV]+)C d8F* d{OdQ@=={# z"?_A}cדb].6q+Lٛ'/OdMPP 9Hu1 XOK +"L q;g}M i) + ٚ'yϮN8 +]g0~ZC8`Ŀ ]Rp=)̠XN+@F#$uЪRU^TAcQ!@XB`zQ/IVorpRI +~ {##}#ˬ>G5'ed$8>) +ſcS՞ZO.dB2vlMP3AcijE'FnFp_2l8'# ߙy+O/ ΀J=!jN&ŲLl7wNoXӫv`{@_;p5\^7иB hvuEYw%(L&_. +x +χa_k~ڀݸ[jدڅ! +-S-FJ'83BT ++C @t>Je=\L&+Jov/IE)kP8)rot/2WwV_7OQ?O ~w}w9q @?# +@{y6dx4ðWp1IHj"C q:"E h hDZ +&&;DA ƒP20\KLۤuHdNZV%34O) + +j^M?@) +O"0^|9hNX@pj&3B /J>ٱ&|N׍~fNB,!oM:-_9pj&V8'D՛ +P껊v%g"̟ؒvUU{iwYV>lg b?Qb\q4kKz +: T_kPN=J]X86MPL +oB 8bGDZ;P,G{8 +P-F3e +֦G0kXV^c Qp>_3z-&Bo%V?Le˗A5~=S:ޅc՛~YxK/?__"&(._ +P̋K/ +O)+"_)ό +Ηӥ +X` jA#)WIKNKvߺl۪Ktx6էꍖW\Mԏ/Jn`=Kʨg#] zgvX̂8;p)cw%w)._4!VJw4_5R'GP:odm i V<Q Ö&/JT^.@/C4o#$T_k;h yg svkOvFb0^rRň|RRĮ rcZSbLn%,L" KAq v=]əgV"ÊHWAX1'oQ|),+Sb].L9Fbc + (Vgo+袩^A UV-TLcK?V +r,x xE0ϊiɮ   v nhj3F n6_ @@'Tɳ4 *`HŁɉc C)@x&^c'ޛ4~q:귭J3?[*j8 +جH!%aӼq)pslOe~svF'{8 ny>o2F&VR 9`|LKO>H [db{\ßE&2 \QxQP'5'rcj:)ߌ~>-VmvXoRU ԉu2-{o78QiP `E\|}pp.DE|5( Q1 +P,Zc(@(ՌLƒ̌&yAT;)E_CZUAvݎBZ] +C2\e2D +#gs +n5*(C6!.)*Q4Mۄ. +PÝc(~^C/Ljb!_>w{ײpJ'Ћ&^n9sNN_ր]g5нmh@Cs܇nH O__*8_>eс g3b]!H +_*3+tMh +燜yV`[ _4Uʼ#O5_ G"I ld|WϣnyƇ- +< &Co dWe@^* 2~:>k%9 X Xɶ?a6 +tK!j^[Lȉ@;Ğ[?3H>"?C2SaˑkZAX 1EJ~5$wRKP8Qw5uF2uRl@Y\/^ b:)Jf#,3qOQ"Pf +وeJ + ٞ3ݪPcά"bF lxn>\ q~GiႩ䙆Z4W5hovK#صX5 ?>- /~ +fy8ݭ!}úExz~>?w%O ɲ7?I'^JM`-xr~ S!zO<B@8gX Cٯj +'HG +h TtP-\S#̍VRd_Sꂮ2%;OX&·_ʂkA}|Yk[88]ԻrRE(NZ"2VA}O` +P!jB`[T:cL&Qv;R0`@Qj G +'sX!w R5}mUE գY}J YK&<E8#䏶rTRS\&&G +@GcZ]mPO]z* +)Pg6 Ȅ^DwL +e cwo!bM +H.:$W Ƚe'5hL @f8˄F.Ҩ9T SYc|RhFm'Kc RV:m] `yd34Y0<d'TX +<6S`3, +?^ +|Rk,2\pYcv(;h߁V#PRaҁv)#2 +*,:ҤJc +H %N2ccWs PvڢUXogX)=Rt<-Kc &h_.*Sr_IA'_GN(+}zO}_"@/M|^`cB +[z 9_\r? {r H\zyySPꗓ6Q)1G]%4:*R>/6HJ jw$oT +l/ve +` 8~T +_ @Yp€ Sp:3Kv<,4^K`0/rHrMj2Z:6JhfXMǻ O"B37]K1Jb9c&v'08vu{K{ 0^yiSoѕxV12oQwx%{)5/ܘ +ZF ʩ;*[]cM5)8JY=cTaX:dUq-.΃dg,m8?|,"f!\%H\l/q}>>j5J!-q}ksO\quA/R$fG4\`i+È/׵V#öؤ,7xEFSG= +5KPzGo`Pc + >e]HOH-wt\&Mj% +"ea⬳g5JT>P@R* +zm˿PYɵ6{$/Lh ?FGtN2 $Zt /T0KyPV|`,Y.K[/m@{0kbb **]ZD{/XPBCzGzoJQf/ +XĂݘsosgpkᙧ{ZZs#AZ xA>0*GZo'?Q l)}yk#%3f`߭˵U.9!06 +Pz1PW +4 s!dDD +@/@{=j_*mGD/AՋF +>Hu}P:ŮgU +u+2pfdA7f4ͽRfts1덽3{v +d :!ڝ/)PY + h;8 ا tq0A: +Ԅ?7V g&%r)^ ~W`܇VMƪy>c` +l{ϼ,ߩ]+S#(5R w@\?ƞ8ޘ7~ɜڇ40P[`\1[.6:BESO6h-P6G. g|:d)8ɤJf8^@5J.%AzSݘŵp^& +d7 o_`(1[s\cr", uN#Q0L,‟UĢɇ9 +^{!Lr9`%# +7z Oo BzῂR*˓`*-g_)_>J Ijt D5Z7-t-'5>ehsoflX)'l +Ȋ][왛+&y] >D̖l(+ƺyu!hH% +`Gf9L:r!?Oۨ3 c+oJJ+bGF\)o`pFH6vԏw֐I JM|QJ7vdn-pxbtTnpNrVq(F)J$|.BuUvo$Q~j\uګ.+vml AU|H +̽6t7L}*FH+}$9wb3}9!JMxzRȲ|#ם]ϲhO~iڐjoY g'N2 +/(Ko$d׌U[)Vߨ-s_%N6F + +ubΗlN! ݂ϧ|F_FtCFWRJWGkC#>*HI-=~Rxu{ֲ-],S?UC{@1z ?u: oSIhX̊"4b6LG W7хy3-?iwnЂe,㵞hajӍx\.Ņ)gHˁV4RO%@] +zM٠ mHYӄH +sӬPp 0l>"qeS, #>e,s޿3p>x8!XnU$"1j:X,H +Ft|*H0\)P':/OI(@M1ew[dja SPK- ?m+u `S +W.|NyyyW^|/9C%ן1cH +*xxH_?Utz^0&w;\Y;@i4(=mϏ=b?_HeJg"7z`c>El_Xc?E.7Ntb6g5&?h6L@eYwWXN*Ŵ9:n~k!́ı@8aKQJm#09i$\l=i`Zdej +_ź@ӱ@<{<r +# +<[~j1X<ᴣyg- ǬInO6*ZiFخ <[#M9+ o]k((w<*9^`U:8D +O)4(PPDZ*ai2yx2r)]-YfPu` `& qB,й%=6Oq~B͘a*k0h+qzj גC Vtw+$s3Q:D,Í0wkC.hAFިh_2JBCfDa2cbˠ{K? +?egk=J{/9<TnCt7~'wy=/s۞quzQ'd) +, +3U߀y:ǜe., e53Pc > 8G(;Oi)d!B+W 폽1dM2qJ(jOY#YaJ.ꐏ@~6R1,c TÊfSRAmMk*m?c^|ZM1.w_`ϛi*ivDBܖ P鯶I\K/gyk}hF(⬾îDc91p%()pk`c-Uxh߻yJT܁b?ҩ7#\a"%&~Q|avL{^_u)E3s)?ݱODZCOd# ^\|*+ѲW;"YvèZhAle4 P|tٓߚ^;0&(5ѴJ?;5fBs*)H' ўˏ>Sػzn"1WaugY\խI +i޹.*7'rȒjxu{뙿nB-6q3XL<b`"[L +gt۔5KO#FhY +8`oheSןs֘ġP +#7X6YxpO'j}EQ! +w3O<=yxn'7upv76U&@EAe5V,a~ +NsVp}]f"(-1A~F;Cvb: +⻢p3V@|0\;`Ar? HsjAefEVڍbs1qkuf5 TZ-Jp" G^=~7GJ +ѝavt/&YC*dI6I/H4<VngK]jL|/b,2WhOxۡ:8LTDYK;:= 8n#?$/Cm\Y-֫U}wOndw7~o￰*NǿuDMceOJ'. =X =G41E|cp#Zch&%@csg + n 嗾7bX3m 1 %t]T|,bTq:p& +|VV ] +"A +rE!d\ ^z*82x7%B)(QY)w-FK)ˍ%H?`2 7aD)qy?TF7Ej/TybIq+Hw7CX +9>-WJҘ>SO_e9o1vI*cC"R.jNEnUNԲCR$='"JyϊE('d<$)@6&l=1 StVqjy[(0^jYHiLF8Ufp!' +T" +ȟ +x|Ek8 +w'l%@m7"tAY){C_hӵ + ?T:GR/j'e3ԦJh[S +`C_p>BEX6DFVrʘfDn"j ?8pGhk=u{‚}ffKBdwjڑȐq9 ) /|c7[r3E&; +1h&B;j*̆ LdVӄKAK-/zs O+g{Anr7\A:՜wZ3)r#R&r3ۂOͷ+NQF|z 1J*@.?R"4k7~BybM +F Uuj0])=SF9EkY/j?jȒ'i;",rM@>@Z~f?badzWEW~ûn`mۧ1mK +;>;/}ޚ|㥕-9yĀuyn\z%5WwyyOR\ٖݬ-\]van|}ž2hSCO&uKGhڳ}ݟoxazwkc 5w?~A;W +z̆$e=7)E['**cnbOzbL#&9 -esѭ/K.aT|ClbCÇ:ϐ MNrr%\_#[74>,{Y{y/?O6-+rg91%'75H))+_hYDPȂ2XVmiXq*/$'OY_2<)(3K{?(ZSEګ^ +=Vz%|~A/'Z#}j?Qß_"ENÐ3Qno0ݙܶvV/w~ڻ>￲ͧG-\8wX  |Ov=kns3խY'WⰿF!wr_#K{̉'R2O*WO*[2;)ԯsg|ݵO<:aDѲy,ҩ/0O^?[ gͯza䗟n F.7J -_`~)+ + +rv#@Dž:*0c‚USޕ@ 9xuhv:P%᧔{3Fa ETS? sOWĎ"el-E4 kݾ6\O߿YO +Sz + (W{#۴!2W}v1=~]}c kVUjU*zJœ˕;҂p衂ɋ~[rr~RC㏯UjfM/>[6vo_TY\2f=(ϰ)R kAg N l"21kt ?-a_`<)uTOoא (nT#R4Q0G[zhbJCj9fءޱ}{o]w_?[4ի}6ur՗wUwU{ud'g)ob޹mΏwv[7v藧^2. w}ݯou +8ﬔyT;T +?~En ڷ)ab33u@xyWj&SϪ +Pbhh^j&6u)4XEt.O9Du=)._}c%EMX0ʁ2832ْ*[V5w%g*zlg&|e31x?b*5ŕRsMN/c9?i~J-?CRXy 8d=zZtJ9??\ʽ Q/$YE'ʶSbWY221}%"" v۹Aɛ<k\h@>&_1g`r^,`_},lەoJwDr-!$^QU%_dQ GW:U9J9m'ɹEކ +ii:ҟ(gV` 8z ;H'$aok0S>NYW{{L=c~K~aorò/7+pikyy獒`O^+MZ#¿<;s7_|シsZ<řf}FwbOpޔG<塳&b]M|zO ~б>n, +& + cA`(ww90 ;0?mSt}Hz; 5TKk& D\S_V5j3(MoҠY tn[ Μ"C.G2y(/ȋ@wsbnF+DѬ$+[n +ƚK7B5jWGO,L l5>Ι7Ͳ˨R2೪S?5"ny,#iG^"ö6ɹ +H¨O3'ǨEgxi^ +Iв%H98=rO +m3- HXIŠ!QŰgCbs?/]Ԫ` +NPrgb^YKo$ XMN,$z.eYZ,3h_ΐ0Q?#B#F +<ޥ\\ݹ}6䀒#BRX$JIk*YH +;6k$"u7 :r=`3Be{$b1=Q@y6r +Fpg +k;*;+ ymlcDVhO +>Wۮ ?#6wB&U**d )]4ˮ"kl3l9e1#)YrƐNv`k\Fg +<ߌkO9 %<"6 P<,myedəD!'x_? o5Xa%h59"C|,Ny8e@^`R)"-[c= #yC +X[=wr+F,=/,>b3E)hs9,=i_6\}zNh3 hq={*al@ja*Z&P$_oPVq*BJl/,w59rfH ;dBfB{_} z~4÷ ryi]^S +߽ib_OD϶B'swe$^Z, *Eco8 DM3?%*üEs +{4Ɖ5֩^4nm IbN|Ĥ&5 +ßpAkE]:"dΫ8}fJ cݙiOBȀڂBõ,TZXR,إ < ;R/4Nn5Uir򗋠\0 ƺ)_, 9$H>~r92F 9jHAG2y-9Rde9YV5~RZyaS)w#E=sf}v,@+1y>z,5H^Hw4"%)y{}vZ W",U +oj_*uu]%ƁbIYZ$% /BS1gIJ#ڞ~ώxtvʥ#\2;= ?yя8w'z[_?vpO4pMUL0!`iAX]d}eO(Zudo)PVY*%ʱ)ArZOJ&D2?39 9.s,{MK$4 兤x5)J÷tDO6Ol9B$C,FjpFҟwŹWV,'k@QqQ3D@s5exbXBy,JV}hsgxbCؼP#4g*F>q#z5/{sۖu pr +r}'?YcqݳߞCs0Jl X"(~c*w>jA nh=H m[dS?t#,`gx-_ u +uR0A2 i +ɧ|t +iI%91t03$t4Pr2/6>@S-2+08uq,mbbZyỦ<͵ +xNl4ҼÌP<TH +L0Xru=S +̑$qQ#hj5_肯#X<|S-ܭu rz +8O rv;(krM]ޖݥ)bvŎ;#^$DSLIL4w 3+ b <4)sIOƆG< 0Q(ܐwvd A{fpkoƒSb=DSlC$>Texd· +} +yQvc~ s/] u3/?IX ^&papXr CAb횐ձMYWmsKFb,w-yQ^iР],[PdΒaAV;eMh{G%Ə/ +*THz?8Ejc@˹X@r)&*>fim[`V91 +B6]V4N"?m¾t 4Ix#CA'd>˶3rbvu/zVsO!&itl>`+nI d,љr0Rʀca`WRci~,1{0RuI (buCtyn?#Pi{jZƋ1cC.d + y/ +oA_+x}ol y  +*THzH[P 1f{KXO8ntܥnz$Ēg 6`? +6%Yh ο +x2_3WnW8fs +* =?yu0ˤ0Of +*TP!O+&8`W#mGxdVw S0t%]ރ$^ ?-j 6hj#&0g/ wf. *w@p }"uVEp !~`p XFVfWDž0='8/ +l!M +*TPB3 bYZN1Xڪ;[qo@_  rn5ԣ =:n24mMp5͒3[i SAb/ +s?3C>AAM脁^Pٟ0: +gA`/l=޷Cx6wfhU@ [vwxS V +f[)l@ + m -@3 #zG6&vQh~Yx{/V +*T4s6SvO.H>wAnGx 0G~-bC؁itt6>w,9Jt 4 +9Ac~X Z7 ]]0^ +NИ2W|0@Z% @LL8*=\]^ʝ|x _f\|GyP#B0%` Ym8" HaA/ +*TPB_ +Ah qo3KYMxw3aF7cX䌲[)'[ +@x?%\h}ZhK. n +ͣB;tx? C +{frAA OIn۳ý.0~'uh+L) 6s{u/3%h ~tMSY4U3Fڂ4Qf) ̇V` +maBVl_EC @[  AO[^~:NŊ,_͛jnݼvM +rÒR@&̖Joisr!i(uL b޾YvMG57x-V?R)׮go߹]Φ#r75ӛl;›0p{ +[u-^gS-nf:k.uv]SG?ԕ._~WÝ?`4=^BqӪݿPa5ܻރܿ'frO#y5ڄWy@уjJh6>I/).A 6y?aU*o2l +Zfxyj{`e0%@CzA߄N)|!p-pP0.dF?&~[X`} +k¡1p + +^~ok +CG>sC_zz=y =pUEkֶek=ҭ0"}>O{Rrl;IA\tԤ@;KiIޘ_eOGhva9s.g^^n+T˯X/9w69 x7 +d}}Ӹ"H vO]??U,f*?1?^qBŐ.Ǝ'ƍHYٕW]8{j' bX@ԩ^X%룝G>Giԩ_Ð (1Oo!W PU7{;i!ef]}B(-rgPN  R%kh{_ThP]g^X @I%N]4&W.=("  '򽁶7ŢQ+ׯ+*m_"~κp$*\~3N!OAA/BZ~Rɩ' ᬯKU\>9 +|䱃OAQϩxi*͙߽u/9}◓:~*8ӑ_G&(  zK}oȹS?\7)hö_~ܯC?~~6ogBEAQo x Ld@>V:wܒ7/>}(  xzWO]}.{{n#Z oq;eׯo      ^xM+-} dL{ E2iL)E@EP&ʣ`V + f[)߃+\Gܕ\*H"%u%!J%qtptt\i(8;̓YG(G('8%Du$`(y֝gu  xk@~ +P_ޯf `^SX8rȑ+#|*| +'fj57j+j2.Ԥ_$Zا\x}E5K9.0K/=',FY!Q o`kۙ2}'oI6Oi$`ĉ&m0~qƍ]7~̚WzA>f1kFZ3*tM(cuȵB׆Z7rԺQ#A%4t,@l:bg8vDȪ!  <z@UgepࠕV + +Z<޽z*խ[@^@.9]}}UފtgG[,E svDMpjޮ/B~ coO\so;Ƞ[@Hz +KN._5E&_2\O.(]|A9k[>+jďxhեsKɤͫ9[-V^ms } -Ү4KYfI^P&$A͒ ǞFk,c٦ hqu`)B΢lu\ a:i$ִ1lc׌zԨգBWrC7fv;QZq-.J5pA g! B ++ׯ} )ӷ/ +?:oHˊ9VDXսbU#oXI=sz+T8=zӣ{N ='00[@nn[`^ -[`~` +E£##-5U"ONΎ% K$1vl, 9&3M>Ճe +㯬*qap9B0Wn_P|_|]ӗznldlj:LitnaEV]ЕhЫsY$6W{ûD~FCZ^Zr7^ ++7"E[ +8ju)ys*SWgG{D3UļS1"W`]qth83-(QnQqa)ninnnnn).;bKË^({UÁj7"lUS] ~q1Agyd{ $5>#_?LweQ,,C =%%E.HmXEY57aAϺ + KI\... Wyo.mo_Z)㜧UeGQEN}KbsW[?yٞJrn+JbXG{<)>^br9&mƶi=u '&~uwB +ZwܟDk +bڰ굣g+CvȽ \j]}蹠R +=.gݑdu6a /;v嗥{; +#V_j[gY;@!V,RӪ-lL\i!!"vP-58TgQc?M=L?? , t{{s+bVVls_ҳġ ۾ݧ6(+cF)Wj` ӥ;f),TN蠈WHTV1T}Z]==w=lo=Ph LN` [-;{7ZRi  +W +Z8ѰaFXtX/^_^wUZ 4^< [pvu˦e׷o_On+ֆk7!V,颎7W^Mӯ^O*6{82]e,@K@'L, rΝ{joL|p+6g]Fw*K^eXSozb.TZ<(&\u +)) pNq +H7("pF{*6T w;Vm̵m3nUxuUS,W&ϓekmej;2cSbϟ+ 7S&9NI;I_~&#mْKUg өKN%>>+EkTJ^Ş. +vl0S1brtvX;Un2K'7/9WY +duibIZv{ڮ>{ xjμ{*<=u .>?rz 1_=\#HrLtpqNw0ooao.qJqq!xnEHDŒkVLP/q5H&}NgTQaCvdc?^)Vk.k]S7Tk6|.^5`s WMJvPKJϡo^-v]V(3{mdC{Oh/[=X7wzn0. +26Uⳑ +?I[EZim6,;: {we.t=õM +]?z}0qZKT+CPܷwHO4DQGkWt^{g&<3It +O_,L{y6.~td1Q..IJeeurse&Mnn7{nkBUܪT -[}p|^s1Χz:ިY5>Wx++E +W#-%E"sJtrX`zlsMڎz,|_: ;Y_vPԃ؏ +GN+++y" +o>*UQR) +EgG{kK,Y ~]\Kf:abpwk9_H.|ة>;.`  J aݷ#(/[޺ +rXɔ<,)T(P,P*|@"G(l"#=-5%EL.M:-qrv[`gac5|II1[tln6 9xMpcO6a%u`:q:v .(]P6 +85񝀭ZR{G`#3V6/26uLk>/CG2i_& +qRIw#ɝ.e*X4FQ%og5r4;fdVg: \GtA +_٠ԑZ~{lxR?/Cjeoƚț+5/(<h!Bs͒αiڹf)g"\%X?In\Ν6 Dq +ՉxYi)p&M.@Q\EP+7 'I+Iܸ3c; ~͛F:`f?O+=`hr26'G3'M]/L:N߫ +^KF6f.#Rx V*BDNlD`\j"Xա*` IaKQIK Abw+Qń$n(Vb)-ϨyFWX] dZ)5G77 KjE^+ZW(& +endobj +171 0 obj << +/Type /XObject +/Subtype /Image +/Width 225 +/Height 34 +/BitsPerComponent 8 +/ColorSpace /DeviceRGB +/Length 1581 +/Filter /FlateDecode +>> +stream +x}LVUFE.`Q2WJh"nmD8F pMeTd<"KbB\'б?a?ͳ{=sݳss~{?*ܟ=M(\Q..(Ge^_[=>:¯ΈJd,'+\#*R=*0>.\s\#w^}\\%yEC.\`goqGhëĹ'yH~;4>qE o\ZJ m%xvQC.Jm3P/[ia++D|58soK.ESPJXEkDg:}rQ (:ƒ_ٿr +6FDWǯ7y&TV'~.57pͅXz._(iT Q-pQs-$ +ƒn&G[N4]TG,x΁PKQ4tC!(L\#v.z+$#X3&Igӽbn/gxOJ;#7t:[P*TNPyepQm@EdnKLn&(E~?ěPY)]){s3=D*EX, +\x𤋎ĊYd/E'F4 +2t@Ԇ WbJe!Q.v{w]k<T:`y^vٝ[l D.WƓKf3ä)%3̾2Y8>h^}}v Rl't_Y(t~Z}wμjՕ?)(QEx +[\b 0bZb` eSV2٩buDQT " +endobj +172 0 obj << +/Type /XObject +/Subtype /Image +/Width 508 +/Height 166 +/BitsPerComponent 8 +/ColorSpace /DeviceRGB +/Length 11492 +/Filter /FlateDecode +>> +stream +x xC+}+|ED/eUXv@ي,l*(zz^ PZhABڒkt64Iwr4dN}>OdfNdΑC2Mez ;aFhdvR2zeydi2r2 *_a_q7yǜIQie uZKH&`-#*$c2,r9>hۖ;M:8ơ5W˜Bg@|w؟>(nA}3n +?0 ddk}FN-WH'ha@D鲡QGJJ.0\J%,J^.][IXUJV:S") kr4䅂&IVS)n &q  -beayeK%aȜ2Zt ɓL#r?˂'KzD?y;՗Y,*"m "՟)~a {0m6d;e+̄W[ #7ߠ|QoIk +3r\3k((寇m\X=t+'. , {0{Haf?Q+ +KtiC&[;*[_ͺswۑzoݱ3׮udV}-6 WZ/}jնM릗_pxԁ?>~'Y8 ++ׅTQ%hs* +r鲱 yOpų-Xf+Ӿp>978H+&U f&]#H 'N_ܬzyףO.] +!+w&2og_$OR>{Jh!L:Y=Nޅ-#p#Z4[58" ~~y|&A:B;kL'D>4.}נnPT50e&x3T~F-5̠pfZ};_jRpҥkjRNǗ. .YTg|uǞ'宝ZL:glޮau_~U Uڷjv|u@ +&B;AaϏ ] ܆pOуq*?-/: +'Zً,$8O2"ԇv~nr"l`Z@40L#YD.FGmQKtKV/ZQh;hOP 0ho>g,"ʛv4`Z.-6^UBpWhϞ#Fz/vMO{D%ί 3nηlL0'OsI~j7};b7u29r>s^O#gݸE:g@ +{>1aB>k +& "koOy NNfWmx Ãnk&Cϵ?.Q:sIl%Kk(7Pn m~eFV4Szjz=c6 ~ +=bos1'K XUtyԢIXݐ]&ڢQ9n6~;K-af +K e9=8pX6x7:cbmLn'krB x{ =ېBѵ;|Q%P{̶ >WN+rZkzT5" +I8^{pg3v0w_gǽ&瑖ذAoaxV=V+ cm/,$6>wJ +,[N1!}A u~@XU`XYJbyК5JEJJ`DC`mD6?pˢ&}6*AC v Ca$:>kZvnOQt9p} +(t[(]x`X--]ZPgk/v:ֻk䩾Cόr`!t,>l +>^8¹C -XASLK1bm>ɏKlΧӾOOn +`c;9_HLJ/z)(j.-|s P<`qуK +X\tKYZ|2EK/RXeˌb~3bBEQ]/{n{^}wN~0uiO?c=YzW؞Yg?#[҇t0HӨ~>79{ΧӦ>ms4[ߝ!hׁ Y/6f་9 wLs/='q&z'\ k_(yߌTy025i& ێ OCa؟rǑa=ylح'5((j`ɇ{@rdT?ոq~!|z!f~ %pZMQAEfPB~A]r⺮K x!U~7;8?:Ύm+,IU7p?c[4Ym% :|V@zbtcZ2G|O_HFHeBZDEJ< +T%:2EMEi&" +xwp| &|lwހrev5.?>8ߪ}.3히.3(]_? R=q׺s !&|At> GA|= :+ G;Wq _UGT ] DG q~rt>~{8_bBm3gv9}i_xm[yk/G.ߵfG{*៱w>nq} +=|qkvy>om O;_4Wm|!9緰Ս'[,fFѪ4mFUijP6v>Wc_e NaNF BPrsE¿\uDe +75Iݽ/#ϛ˳f_ +t D d6WȮk+Muvέzw%<-mQOyDh%쩓e\GtUֿ:odެ"(;?WHCK`Xtu<}xfhI;_58_v&9ZH~ƙ R5.^)nߎ-ilH Z^wK^=5j{e3Zog|v>0&M7cl:u˙?ܖ;?*Y `R+Zt>:M讍nf>ya0:߻?*UaY8;"D/5`#|t>As!Kj08Uu9j w5 Χo/T!w9tX 5ɗGLiUӮi<Ks(Ac0 OO>[!aߟo_0M;?AsRՈ*di nݭ7M<0?W~swGc0t~+;FDc0tc+[yyBx!p9Gh4UUCc0t>N>;hac_i@I#4.xEc0/亸3㕕T3pZf^1x &sIKQ`8!ehZ Gc0Me|tse1S7\ i͆shp!t>W[,fFѪ4mFUijPSt>'¯U3㯁>_^bInS}SX>F/nlΧԶr^'2FT[41Sѝ~=u~h5;sS%> +xLKBY?sjD%NH )z#4*EYi~;Qo-]Tf.ƣ1*G/gy&1H92-ߟՂk4` +txag9)juUUBYq.b032ǫ| > OF>8hb]%};\P+*K%y|܏0_O1uq)f++qf-#: cRhs3jk++JŹx܏0_In1&6Q4pB Odz1*6Sw*J;ߝ[`$ϱW=t 3%iͦ:nϗ[2j-c21gw[c۠].jNJYr3dSic'Dc0LKBY?sjD%NHt~{9߹egV;`Z9G/gy&1H92-ӟwn;Jt ƷFFxu>gtM};^q^w~m)`=?$ťĥ2sI PY|1_In1&6Q4pB + 6f +:q Ӛ eĞ&!t>Aw:w ^z.A+y`0||e\1 :߿oߟ/ T+C |Aݴ}  ~on ,~+@  G;8rn'dt `Emha ۖ/A/W]Qw%/b^\Z?$ q~c¡^ +endobj +173 0 obj << +/Type /XObject +/Subtype /Image +/Width 1024 +/Height 768 +/BitsPerComponent 8 +/ColorSpace /DeviceRGB +/Length 423497 +/Filter /FlateDecode +>> +stream +xXGO_$$v+XPQT"U қA4Q ҏԻ Y/G WP4sg۝qu@Cw3,Xϰfyk`1 `0 opH5o{0@9 +ZQC=d S#NHY OY U +=,.?,g&ic6yǖf"~\X{M2eK +?`b̡12P` ~Pߡir- `0̻Ksqus c9]ĈMH8mgn*ѴP,i+,& +I֍9X'eyq:}{"򛪤dUZ T\TNk^S3v-l*Ȓ?]Ӯ *k?dHb0u `aX:;/wZtsPܡx!!sqȆ!y!nE8Il@[uH>~-L({*LjA&:MW5_٢XZb|LޡZ=c5bTՊVYazy#_?0=LQ#T]U `0oU Fu˝c\tiy³CӹCCΎwi[i%]Ԍ.lD_K[:P²d۳"E3kq 7'h[֙*AN_VWV[.Z,\%\|I֖~Nd`?FzzsS(RJeu><{*1 `0w0} W::u(_8|! ~$} }mљg1R*?KW~a݂䟲5l7>Zo\gxdxnjŭՊ[AR*.-[%_k> "e̺tB +AL] +w!_ @2PȟgӢ9ͭ?HRP]h;H +nC~tw"g9-ug7$ʪn-7r{w񘉨I k +  +hy +yf/F8[:f `oУ)^ ]ήCҚ׌ÈEmQbߎ~QV(OY6GgzL`26ԙ*~ju%Iկ^[%rWNJ?휓-s9_mfh$S(6|,Dӯ +p.h X be)q} +hy  G 9`0)-i%cI65ڴGi VP O@I'٪or?ɀ(H מ5!Ԩ&<̨C̲?to')?`@qD3@J7SSsXiSuDeɵ>@PA8(A^1n}"фn4dѹt:ڙӣ_>dMQj~ZtUq?yٮfAŲ1B"?GLۋzzp+Oϑ?A,m-.vʠ)αTE8viBJ%eO?f+<#mEQd1L{T6~I6`06ູ\]ܒ.q!aM<˔͈mqRmBMuڏ*?(f*Δ~5Z3Oo& DcZmqbMq#8cWP}fK&2!̡ll"M쓳.AhQ(( +BR*Z?=pPxW _%[ +0F^VtG]l1`em+;ǂI&z?l?:]";q"s=b98NT1@P<4˺{~Zt?A!>9~et݄ٚں =aQ0<\}4c$8˪ZһCo?:a՞s S`0 aa}!m]̜nj!z.bBdH2/?A_x>W>K1(c:}*}rdiMѽ Ğ G + )Ⱦ Y&?й+10^tӧԏ&,ϾK4E7(&؜)D pe2_qHΞ%`0 9SmV&}60fu|Z:cR#d)} }1]Șܘ>t&m˶ά\Q+ +-y5Oݎk8`էAs?ݍ?c) ǜwf}~彻4j$_qsedM4VPb;"ͺcS M-?T"b75h'[&t& BAz>wl'3Rڎ?6z4M_cwXe\ +-j Bq,AS< CmhHQV-$dK `0u+_ۗmX6q;ݎϵ:>QP}XUem Z} +C!2xV\ +U +OYBY>feS>ek㶕)o +>a0*<0Pƺ `0 `0 `0 `0 `0 `0 +x=1 `=c~%(bG9f?(k^k8#0 `0w}x8+F˰c0 N۱ڟyh-{ + ? 7_-<Ѕ3k<21 `p(f CLG,1tn6'"1 `01?;?Xdhxo2zpU~a`0 !V`0 _)0l1 >`0 _)pb0 >`0 _)pb0 >`0 _)wn75Լe;{gu|`1r"JSjsҭ [o;ZKjfIz3ӊrce' +: g&T2ӨAk/מl(6=#$<G<#PJed_+PHo*Nwͭgqi +1שV_b"Vm ogy1 +*esL2=fcR]",,[k +۶3Ox7͡$oB2d+ մh +$ +*>=@<-HLCM}=Q!_1 wS +si3fKHGe'S + +>k`eFQQ +eӰEd:=,Uv)6 W\s/]̐%afX]7BkOb=G +*dI9s8G?.'Y8x釛PҚw8o?"|uѭ̪֢ٵ +隅(76JTGc[.}ܿS@϶Z VVWغS@ObKVM-+&Bj]ZaRR4QO]}4g(~*pMA%ʪnn1,te6[zaf˩_bEq ۙ'<ćtr$Y1 +3(I9tqt_LXl +ybsjI(+ xY 7̀u-Deb-6B[_bm/ 9q + &(h6U +T+=,:v+i`D$*Nf%(&^MHg. +Fb*WTŕSz?SRjl{@VQta%5d +?Qjru4]+:lTb,3,sXax/k鑵TewAFM^t¿X Q>ڧ'epLD=5uG/ڵ{~OQbom}߿~u׶9bmTJ%@QKd}[ XKEDB]&(߃{gX_ٸWa˴xr[euJ{rTrnvaw3ԙ *ܸu u}Ũ}wYܑq7W+㭋|Q ](8~=ARNYq -*ǵUqun~awK/MIZV:DW'id,9P?\9 +B?(l~&<۬*g檷 +VkA`ßIQQ>K EAkǶ_CUH@!m?_a8$_}Bk Mv + VW:'q=j +ۅ$[o[^'MuuU4qfO4D:e` T堚GfQt6}ï*Gd2NxWG 㨢 2v꫏o%F̮"Ơ`(@D >puUqPx.9_ z|! +OSjl}責E/*ۊKK໲FpFGc"gtt!glm7^@ovpfҫ +쒞jE_Py(;R]l'Y{vRj[e+:$J\c${%3Oݥ;vYzoV)<6Ͽ ,vE셚3 e%)(3/>g b8^},&9,O5}Ւ=S킋kc3ι߰ᐤpsr0s4=l2l2Wyn)?\;kẏ3G̣~&|FAol@U C +F4bz}'a: L?Qځt +AH5"{ MueIh`À +>e;f<1_7ŋVzq+\*9{{~Չbj-+s +j-G2Ï5!E#+>$ +j9`mohs䤩.j*G 6`%&OSxk$:.^ m e@PVcu +`87υhO~3QX?30/jVkf7p +D7S=yx2 +c#e +./M! +*VXL>z]2SU +2Gt͹?[*] 1r"(nj;#5"ErNoJ^sC2rmo +ؙ߹>GONY<$l XjM6m^x+Վj(y\ςp,6(!AW\oHmk^$MB1 (<կG&=6E+d +LOZPX$=/&)x_WC%QF9\JJ!0wnVz=Q0o𛲁O,cs s+m-M4Zo~f>4iy$)ێ5} L58}*VI\`_o%V\Vq[a*>w\*|V.cMZJݕG'+ 6svYi%J2Rt! +}7}ONھ&F۶ +%t?:*#itw]rZzQgny挭ݩA_pƺy=o×P)x'Exל|;_'|'->e1E]N,|I ul3.%}~Gywe}vȲgY|i'JN~K>_϶&?pdv&[Qwi'Zqӷ5+ ]Pף|SϜUiRug\ +w 9qm]Frgb3kx) ͲN?{{ǝZj!oJ-\?ty"0u,̒cIxv;c Y4faʚr-"#C7OOE-7U ÄbwGf.6т|+$5eҔS~ִH4X"eOޢ'' 56dG: +X(u +;D~>wꕜ +$.<7u7oWҒL3:hs;RZCz_tu^."YuWs(-b(gN3G~ On[C[ + +䪗[x6gz酕.UVˈh=^aqURG~餄: t$>k|Q}j) +z;rϲ`ò dYN^c{3z ={N8Ӿt+L< Oz z +s[WDcײܟΤTB$ntQ[OOZI}SgY:xz9{83w*1:( EeW"[O6To + +H3n8ӉYq;>#Ů (KffSS"P(}p{l{lGlg\W|W|WbՄrrRl  +[4h +3zz ?΀+O-Dˎ̊ᬊaD:ǧgĤd?H):Ɖ2j,ﯬGU!\d-ʎ B??"f`nv駜C'b7m7_??b 3SD*uFGG'F^ت=6+++';'' Xy5y#lb{ŗ]MD;;+57FGL2I}ń#fPQΚ +Oi_YV2X~C>@~{ʑjTuj./BϭS[\ +-#g+h۪٤r&˥ +~vX^].0Q #*7˗Î9\5+\"b:g"gJN&J6NK{j?C{W~zӖMd [v5S=ti3OKM +2,x</L +Ym'|bwgN#OON EC׮((G@:ZZښZZ=٢+Pb/"5~)uI5Iމމ%W lW|WBԾVREFwbЈ+pU/`Y²곓Si"Td*L2m'\'zh-ʊ18[/?~܆l,E^NH=qqqTn7Bx[69iDRjsh(lE59*Բ,EwsGw=tm Î;no'>>)XHtGb%a+=oO׬{x|FG'[NEڏĻش'+.TD YCvϻp$"rdݭvǘǘv6dXE FM,e7iό\э3=xv +`e%\G5mJ>-ǻGznA.#2;EQ㟚G~?߿^~7{O8Ԉ:nOOgMkR,FZ,R}V w=>%BZΉȁ\(Y(*A +KG'*{zN=UUۍ +Vzt$ [=25->dqKʹ^7Z6㟚L1\nV~w&KmQ#i}ߒ~kɽ;E1Y +__LW3xV53Ol*%v咋3_FH4z?yo,ܿku+V~V5wsP} g5 9CjXwPgv^N<(PwqF,õλxd\DwOsMv;cu&E?ݢ\|MIԉkyιa9;s+C&8d軼sEFFm$GT90%mP;g\Ro;0G:cx簩Qsȼh§}*rd&eF`)$S,Ԛfk,EIik1zmгn(vd&vd,yx8wh|xR(Z'_$s ,((Hpu M/}Y4zu=l-ڛF%> *+#]KR;S;л) x 9*ҖGgj/G7=0^ݞucm<Ӝe,P;~/!+&O5· RD+$/cfl-ZIioRVuf#n- +1ղ?[z{빕,>RYUQuҥtqCpR/vZԵ(k)R=>mz=>!Ximo^Yq%sVki- i)e~IKv;Ի5{$tvwZ\\"~yꆭ{t@pRzfV}]Rֶ# M?tk?ֶWB+=K +WX_%V + KW5e +?Gt Rfȿ@U:m<%HhY/15<90'eEEBQP[\vݳ + ~DS/=טsPwNތNS}#]BtMS닭Bjd33_-Kerr̷{9+seڮU~U*p ]m Nl۶USޭ 俻{ 7fg^dYȹStq_OkL LH)OV[gۄ؅#_piB& &}UM %ѱ.>q^AQΜ HIͮIw-455#eIQu_M-#7|Zrn>G Rx>T]ҚzZG_yWe3Tˆ E K,;WAoݿY1ƥF%%ޑ*|X>(5l&# +Ni5ۤ~?y^SsI >I3^aQ%9gmU;#[Qz{Dvy7xWpO7H.'MI}dgp?W~q7j{/|rx!MV3ptBͧ3+( wJs& /F6 Ѭv>7x#"ﳝw,6#jA4^ZإfbC4L#l1rshrTxUTo3*ۆ3{f:fM +ZLw߼.n?x@k9VhtW揓lU'ߌZj~KsCѦMڴ&L۬糮O^|2b]*rz-I7KQG{yO4`4!QY[T{Б#֙%%*ԲQT6IMN˧̭`%mmmABݠ(s3# +pOy"u֑9PPPynݩrZ<4*,#Ka 'Xz-K&TY0Jq,|Ѿf 2!A;KmY6KxZnq鰤aL>.iԏIc;g3zᣛ}5攦inbGm]e󓕎vH 'Q3YWo~ON]ZOct;:UG)(="kM㿸LG܊?V|إpߕܝ->^Not6~}fE>#E7'Ke R^7-~WT{ |F7RbZƣZo4ǬKF G Gj3<6]@0.;ܓ%0{1ԏ#-v܍7eYjRu.6zXϣ7O4zhH 9Cc3[h"ƺ|j99T4!kFg&}+ox|rvɲ4G\S-I yz`U9Oyl(d/7Fjk;%jzٍ4xAjE oLB-,n#KZݏw<{i-\ϭܩK;4߶6\)(:7> 5X@V^w]ֶi?/iu|kCclgƄO/G+ +EhgW6}j}ovG՘cŘEI腂Q3q҅r<<hZ[p-1.#ͯy{Y[~hqGͷm@,_Zxe_6ztt'!)C4y3a`eHSGWS1G z{;Vn4@<YFM?3[yֻB_ѼFTKnh8&,",o#bxg2?{]Pq/d<&EbYg}jO=ydr_g\{}wWm_,aZn.Ԃ | &kE';"r?af먖_^2g;*rϣ;NꮰoJ_گKKw]D\˽w;cxTX֠w;: +L)'} H?@g#r#KiRqT\Dɿֺg.XԷ +1`엙QW]ד^|XE7ڜf}g_ h/wj:.#Z6TH-,g5N5(xSeEFKA}#9aITX+qfj50Իhj}zH\p['3~WU/2٨uvͩ}.Rãz-6y=_W=#=]yUz +Xq! +q>2||(2 ^8OSK=omlnN<3VU +JH49. Gz{::[[Z{:kk;jj/-iT# mz=YfAR/E*4ǨSZUFAoվjz׏ky+:u +gǥ ZծJԲ1<s_H̟T:_ύ[$nzVFƻ'S:ơWż+?)Q}E|e_%\CZZ+e/ˡ#GW +#?rZƎmu _wcagB :;9i-k.z-&aW3Zg7 _N(_KC^^ ML.115z\dpLN}n`;|@g#A.>kйe{g:sS;Nkz.YDzYZ;ݵϞ8p.]gj_Ѣhǿ}#|Y|-BQ+_u=pѶ״T magsן}TJP$.Rá,cj^5I5>w]~}c>^͈5w4m?n/ܢcZ=_ 5sY񉭗*ĖK8}{,=":V{[}iz/Vnsppppg=rnk8XJ/-q[@w^xϊrVN_/y|֔n>4РF}9kQ nD& +GNR +[ҝ!|ِ0vx[{a)ód[(s|lAm8g}w6G ± lncb\8{>rB8bŸt8ݞ " +KޞwKh{D{D2?Iޚ$LdC_NIMMt1lmLNMÞ;b335 53uҗ3ҿp}9sJLvGi1,ex䰹1_ =9K2_i7y988899@ X^'99889_KGq{9889?9{>|Esp?su/<5uF_Ϋ?s|^x^xekɇSzy^x^_1 / /`?/ /  / /pq?6, /?_S/G8e]^xᅗ /spp /|?/ /r?? /p,^^x^9 /}^8^x_AyG?' /988_wM +$"dJVDЅvKAD"JeAFDP.4JA?>Ifb$@2 t +$-I@RQ/ $}0kZz2C[[%&|j\KD*K1AD"dwH;%kD`fHAD)ܔK>x) @"̥A5T%[/PTccx.!_χO#+:Kqt5GsP"פ0H"<,Č"hD G : +9')fY Ft"|J)ֈ^|=M߅GpYZ%s AD(_8 _Q$;S.A` )D`ǙFK6yIA/a +W0#hc!H:֝0Cpbx~nxBn1o7ɘ@z +$@#U@R'hjH2 !,xjCk67t=bYe@w"M +8tİ6 GΌi8ch>"pq n?kktƕ,=|s,q7R䜎WɝcR_Ptc LdΤ +b!3 n.d%SC=a bGhY>f +]!Rf.;!`m7d/gg7gY,C{9AZ (|0) + +d?0$ :rkmd֦Q4҅; + w4`\w +)"&X^yWMvhfT9i/5rLT@ &S+ + +Y>u?sV Eb:mBLfFmi|$溋L-} ' pe\ +L,@#8Ɉ%ӍD%K9.;5D`j[w6S20Cu&8 +`N'Q|_XG@D\!&AMMH 4#J.I7I +ډ . +4߄ik a״ Q%1X` +PA@X@Axd$)ƗU{V6$[Gl<ڐ2Iwf. +u;?K}~o z$@bל/Qi۾0טker2dsD~>&S1LLGъ,c)xv/H1¾n|A!$6Ayp,$џĄ2@DAN/x@\CX>I +9^(LG!dVɀ(4; D3៍\sԽ8fBe {e%s&M t%$ulo'iTnr +e +F:k&L׋Ǯ<!Cd"*[L/dbҘͦ;H2Yc +1ؼt5(71TqD"[--b L@ 0/VG3KrZl3X!hŷjੰRFD1!tRR2-` nGf{lH]L4neY= q l?V:ȿ.3) +v/EO6H 2'r_@Md bAnOd?]~ HT;Ȥ D^u0L +E]z__ +0f)8bB t#C(.F/!Q}`bv\ +Y܂±xH!kVPDXٍ8e"2.~/|vp#h}.I/]sηfb/Ȥ_D¨(}edI:Ț)2R2 ݠBIH]#o +bS3dOˀf<d]!0 pib﹚YV4 })5\VSJJEj + `/C +B .׋7ZU5+, +.^;?'dM +c4.Me'n!% ` BcM4W%dV +܋Jaii2 .L%@Ğ +2نɴ@ST b @C ++ch~@.'4~1QsLҔڬ!a*)ِ\`?t2nلͲ:9/gPOK(R(z9+Yer +e7NХgO.L&+9/7zsD'1]A ~i &蒣"V)9)(5p/ +ٷp l .Gty+Y +& +_zI{!U3L5Ƙ,@㲱 +FW6L +`uDܻ!lm t4Sd m_v +j?O40bbeSt:(us`:ttJ-HE]D@e" nb=L +):L.22|EOG"gl sšV0_1؛_KӁFAn$|GѸȿRx/(MD ='筁a5"Fћ̒m̽,KoF`J3C"~e?ݯxGOr/:YU}RۻZj"6_g.]Y{e^[+8է$ /_B\_n/v99rleSqel,uF7*X4s<IZ#̟(k0aP>e8@_ +dE:1c3 ( Q偼&?$?*%M2“$@)dj٥~i:zpi +7UIv3 + ٹ. K),L)b?% er@o r< &|WHdIH.&k*  YS0CkQ_Äo-9W՟͹~Xs{W :ޕz|봽*+[l>ui׼]FZ}ir{29ny>u$ O˵.Td$ +B{@q^i읝ͳ;3gw眝==ٲ-˖e˖d%˖,ɒ(QDJbH9`N` + +NGM,P~M@ ?{~}0!Pz;L K ++ګF(t;Uq =),`'U^ʣ +ʹ(jz_;U`'[E|mˊ +}~mĮ\ HF83%]|hB_$X(_FpW[B',DJQV2Mzg<UG1Bl% ^ S(g3 +P'Ȗg$5( +vDFdT [_oxW.pK +C!}IQ +CeqJp{'Pua2yay |Y g +?Kܟ'b T DYmc_/ߢдC:t.r5WT +8QS#ny\0 +`aK%@$U0󙘤 ߁)?llտ_ RD7P@ND9޴ºL2a(>!|[-~y0CܠNHs@z0sf9 xIӲ=a*AA!,{)0?5ijpt+CVc& e$1Bl|gUʺH`Kİ?ɁD1X ]Xk)6: +# sIy9cʸq*{)o@ e/2VQ#xkC^Ҍl +7lNb [J|*ߵ +`ӘdчiLR_|BkȢtW|/Z렢.aT]#9aCG N,`at`?{mAe, L]B P+ m \Wߤ;16by˾rmw@-U۰mW#DGdѫ-񬥍jHvW& } 2 :Z(jVnE#Nb>3U8Zwms(zƥ"֘<+OoZ΅@F`zBl>*{PV܊'ϝؕ~x[bԡGSn'1W|Ƀ_yg7?o΂Uկ>UL.('_\[zkZM* +=r>'}7?>_>1UZPKi_u ۧ|xl(H>ۨ S{ϧ1;<;PBCTl|J&ErҦ=]\*5&I?BpZd̔ @=?sepZGoY)d^P O,(n+)CuLIF^BG$ +к +PAM5n-O'=O@|5{|Լ;/H\\ N *"rh) +I\kV687CF^7ۖۋFAՇM~||7.6`kV4AF4oTP`,MZ"!~!~%Wz9C*؈F5Uz +#NZn_(\E.#< /:W*` EY7]!_V9la١`j]!N'deh1p/ܦ&/7!i]h@:.H@umxO +?*0n{siiEWZ h2e tlkݯHW?S(tVX@Bʑ ȍ"#Ԥb9 WF32 2+W劯 gJ΂\U/]^9G`eS$T9_&iS#ßj_j7I؟<1w3 .C{3a`mB\ $ PF8 +֬`A D2_`=Hnm9Wڔf0A^C ϳZ>|ŧq>~J5X + F@60~ɹBNYHaP_DW[(*x7 z?XOS^o??Lj ⱽO0H +fB a#bH%i"m=F2DLf6"F0|t TYG J2ȅ@n" X^ xRG+v\mGP䀐S, )(/Pp2废aoP} Q*4 +r2*G!Pu>oJ\Ժ!uY) +PR>:xbŸX6lG +3~04!8x 0! D +!o ? +'VW9`*2b?D2B 5e~ Bʁ[ϨD$, ,MvC*@qC +W^ c8NY9aVjKܩ"RjRW r,&(-.>d&r\j+V=Pv3ke3.y.ϣ|dVh-!=B\.%@fЕ=s Mj0(A@Ոn"`n= +Z +`;zsFJu^3M/e㴚s,5oȿȿH+^)uU-rv6o7׮4b@ q8H#dC+I[}2;pC[ "ϭ  Qג6|-脛ټ h#";-- ˒+ɂ +/w+ɡ' +tu@Qn1#F ~n{ !ǘpހ O H;Ti?~.X-iڤ2z 18[5WPZʊlK9.ezipYkfLn1*`t(p% Z").*+K#Q4;qGAUy.íAh + +g袌 +Yճl)0RT+0 +Շ^8N.t]vP Z]iyVkðjeG ++/('\"V[5f +TRe d(K +ktV+Z/gbJF*pn̜V2m uz+.KiL[W6~="MT~VA @BNABuJ`)d +lmq7cOj:|+?UD)qTZJEn5P^M%?,՞lRnꫢ>yb7Fu<MzWIgU(XNPBBEa%_>DUX7Jſ)TG:N[hc5ީ`˫!Hfd}k`~;oAڝ7A +qck OO s.dDwIyvB DqtڑB[%H j, +WŦ,M8is+l} (A#X;p+u9 m9Wg>ӤOQ.Xzj: ! +nUȧ2Uo- i< 7o<C]XVGh,z +ԤKUk֦]RӋhBo ?*C%C`)ge6407p5zAmIhWsT +>wtIPBE UtbI@ۘu`&,4[=V<O{R#:Q}n +~@T]DĔpB7JqZe2H_P./Mb  +-B@c,.K].̦&*5 rbM՝Z!%UGy@`~ + PB?y0*Dn\qh +1yjUY͡QƁq PV)rt?W{{>[`ٸ.ϳ+pRGM2v*`p쭒eY%"Wm $U8@)5fϏewLc`/;C1vP}LZ;`|2_$3} ǤMW8p/;]#?%W J2e t~Aj],2ho>kŘ?qkza:RgX!+g|>v]#-|J ++`Wn.!]Ǫ|+#7J1K^sCaރs%vM@|Yyۅlf9z2dd2u(om-?Z4"!`}k pX>M +W.?KCP")sPzi}t?"4G +6kPc LiorؔBy%C|0Pь hYbZ2mD I +i< !F%@ 4vzaeLa|ZPߚO+j`Rfzj +hFvb+_r~$8ot[+MO.G` YT* +lREzW_O5/*]CĞ`RxJ%@T@,`^c /(A Pkk?I⭰?L}0|~qx Mpz<& ݸ"9>_|%8`WZNA`J2oV }uUA{Gu8+Uib`%)"8lJ)i.@nLj]q | +99 +lJxWs>30ɏۀ# +*{:NK# pj^CP*;;.MhԫSh3𕲩JeC O rplJ dEwnJ*yf㄃Qbvɷx~g^Cf4FY "C +K/8R|*YR:i4sԀ 3AoRO !;?VIK<%x\yp!+z {!Pԑ[Y'%[&d[Hu Y{K8ӮW1%R<[l'%6hհ1y ( +Ino9J +5%c_2\z$&}#A#B?rnrOV]ztOyvft3q陌ᴼ) #Tong] +9\%w*mVиe]UT\dUurH6A0ow.,:;-+r]sSnk7LM1dV~ _VB*@ +$ΏGT"/-(UN(؆ۈi%w"eDH~9P" +43 +βu&zh, +Y "Sr +s8̷MfS);Ú2A܊T9.lc8!A&,6/0\JY(k`l& 4##r)O6P +@313 R=nEM/qgky[6uuUD-DO,yWS-M?h +oKo{NJRZ.7ΐOKN ֕HV*wPtc7g4Q tɏ?{}w:7RNxiA¯*.J~?]* +q0k7F)L +H}])fL@IgaRpQnkΝS]RMۭ^j6Pm >㲀s' +rRםnw@HlVp mSOJ< 't ?]yQerV~/lOnW j Y +ȁumZJ\["X]P]ǺSn9n꺯׸!C|Cΰ]mT6<# F\;dkڃ 0?or.z:b+R@es}#%@aا +^qwbv +b4 .@fFo b5@c"_? +&?3|iG)P|p|Ky[fsP' 揁LO?Q}y{q'?v's ?vf^ώ.3Of>XJW?Mb!|!17_H h\w[ +#룋 T0qW$V # +h3rJ:ٿ:LB` +۱ge +u 1t)HE*Ҁ} +)߉5X`D#^R>yF})t?j܏V %RXmBҾRU=῁#Kn?`n +C~ *)Ԣg*Pp?ݲ~*Ko nZ3yM}mtJr?CQPQi?&z:?n9uulKoڽ:sC_UŇ}n?Xp{ N}㕴/p V$nW(g Q^3<0ߥU5t%ʰ Mdg!5| OY +<>ߎ]H 4h=fR)l_o!}3 +cF1tmr˥ +@(7* +gT+VU~Aٲkn9%9I/NϐME17 z*#S@zfPb/9zdC@uVא-ck_ F +JnhuWQ+?'"%y!X^S!e*X5=( 0| rHS[v-|{=|ѷq<kMV ԈzjԆ fEyu9S]J7N +^;W9y}E(=*9p.D#B:h +h~obz;s@ +ztO#H6 x\&PQKW +w_u+p~OOGWW[{{c[kCKsmmUIՋ=mߚ~E=m>췎<7>w7W ^lkKl:I :g=v韹헿Ycv\{|7?`z߽ᑑONWT1VIYF|.Z*|jԿo%!ߗ;L߳`YsXfuzQ*Zl e?F+mvϓ6.~Ø$ ;D4(0?d$*´}8lB*dBVXl(xjŽ|+|"VF. + +e47'scp3]mx\H) +6Bצ\~XhvTѺW +8K!‡G]4uԢNt(OWaEJ#F r{(wlJ'Arٕ 6bLٍ]̎V+Dh3jGjif$nrfwMAM$H (PU@y, +eP +[1aL,L;:ǍH_)aK젻@@A~oӀʁ,Q_Jm +joG lBq|QC +fi+WO 2M*C0 OBuw$w-y<ĹZ!O +I00n\1ߧZNċsDL@! +)B_}?/|鎇{o73Ry?'nW[Y柰QN}w +iP$) +~H #.iS$rB> :4{99,# +8)6?mE/Dz @ 0PWdAG8Fr,ܐ!ukf"Zahlj1 @K8,H ƪT}Fp>Ob?/'"͉hdW`v? =9'ILK +n:I:$a.we/?<)4.Yn.X\.b`ZH-qVz`džN U 7 m @t0[DP فgla6$g:Iԫ~8 <VrAWD +iESF\qϤ`Dzf `̥X{2\Ι5M8l^GR^(/uF-JJ d{?o%ZLy WD9̄ +:^Db1(Gp[7##8f VHL} u1Z2h#B2-FAg@zb! +~Vw&)OOPZ$ +NS/'VtfI| +*saU}ˑ + f?'MPﰇvM~V|ۆ(jP E +rko52c +_XjvFdٳL_M=+3WIuلVʑ,/&bt5EIοՆE$(ZC3 9+Ir!>h5q$i.ly֠oP9 +4P5<7z[u\kx Zh ~u z_|G޿ ~AwI]p'/t;T$A r +iF'UvS|{X [) BT[25h^9WD>ur4Ʊ9wmlb=lc0VW&U4(J 9`A +LPҍ8Ӓ¬"AJ={#,m;!o'`SԾ G6C ?*wx០1X1?$w^/V0hEi>߻lm'@fc =#; +gy3]n +R4O! +}3F;1z6; +Ў/4HA \LVn-* + +`_Jfyd ^C\u X+80Q9 |c˞ [r - ,Å$y9p8xX m'9s#ۂoS Vǘ)mt3͚VIAB^'@}- +%@gX =D)V ?XMȹ!vkOHUޭ^t RiJFh.&UQq]i: nSvPE]t8m,%@]&҄R@š9)+ز+AOҢJv_~ \v5Ou+`q Ee.YoU@<y +xv;LB@$Q0pԵ6Ay i6jw^7O E6ry:= ̗`!(eK,")\>>t53,ܡr!9.J`M +A `+ ~ (">O6E$xwe4…cy kI> +eS89z|ǷPc0Htc<<+b#;v85zhJNg.>3"N8I~n(w9y4AM26-1re:]q0J[[!ͤQx` +Dcamd +N'|= hB%: +Y@ P);z@v4j > /&׉W8F|ϕ_QQd:P%ڍVRj܄K> e5C%)aJauWжb,2aӵ`P*rߎ?% m߲'PP*=wNޖFd@H_L`1,ES4`ЃDz@7,K48 ܒWXzgemm+0T׳06 +o2zwς?267W>9,ȃge Fl~ywa+DWq~2C +`OEGii0Ji8gLJNE8<:hxq1 rqJb$1Say ]K.ݮf5cU{<1 Q&#r޸%w$_ vPF{gٵ2mFԷm^'8?G`֚g{6nB@o,E|C a^#,<ߏu׺4Ӟ +zD=M]O_K^IDm~UĖ_2}ޙ=3;x * Kc[:e8B)e1S/ +NYسrki +A?05}ZM/v=z3̎kd|ѵNgeOu*Ͼ%eˉRmn~hzQތ`7ŵg\囦lKȭ9)It NZd!B_K|V@VԻ&x>3Tf +[ϽX87Go}dC3`ϻL@!tFSBK\hw BqN4 ة䓞*@m޽yDـI L5ÿ́v~oq͂+҇s~f F +\yad7^݌̳(tӆ]#2;h#O >|PpEB3e#ςP|c̑=^soxF$ +QΕNz$ ;: *5!Ědy+ o=2Nov|0 ?Ev߁/60o׸ %@[5__p) T̓⢇ +?Zx{x8߾mCI%\(E킧_+SY' +KLZAu 9 :[(BU@0:@X]0Md1Dw `O>p+a[3gR4_c02U&?'a&?gi9,>ʈpxUGWny:Z(8O_EΗaU D( +( AYы,Yq*V-^VJj+19x:74<ټYa +WRJu?ʑC+í=U9EwdA{I ̾@AVTAL8T˫='IwI?Hg50s^*Ei?/fctD؃WAz]4Oݯo?w6~m'; 4=?g^xĩ3 ׸_4}ώSْ=jq79"` +xMRO`e;lN>O&&4v*1Y +*Q?@;?B@ ߀P`) +:]']dd%6t . +5`˛j[2E0Ku% `h)?SѴ6{MOd-+pWqu +@POc9v=U>|!yc_ZU G,Ks~k`_*=O }d_ +Љ@ <" +݃zFIz|¥v  +*h)ɥHbmƯFh7*xQ9yw ++z a2 V~L.[oQ6dwZzX5곞:f,U ~L% +.w.i}"'SKPO/|Z`Ɠğ}.F ɔ@Q@ +  +S&^:Ӏ~YCDyǔӌ(bqXI$Dq>J}D\'GKI9ʑE1ېOl%h4쀴j2)"Az xh(W~8MA{4Bt3aZ'_ +Wk<"(G_ijw?+IV &ƷΑ7׾΍n9Wk7O~ _~͌ŅO?xD{.n:,@PpA(2 +S +,:+P ZO-8(U@NAeN8 )_~o`>"eG80.lLl[P3%ҁU` +k8[Y"uu`ɭ~$U@A0}S8B(%@U i\?bd',_2ĞB:uLAir=a> `EPVfWA9yf_0nc -j)|l#8ʋ>I2c)d˩er='wIVl1؉28P--@ coQ>$-+tirq8Vnr +gl>=NwN.\vɿlۿr~>_y1 +l:h @p@?[_ܸCZa;( 99x>Ef>%0'ik+Y wm! +-)H\FyMf +`-@, y#g>0/ }4I!Ww.?* P$߼i#s߀}+3O۷PˠX`S??@?T>U +k 4X~\49B/UHG]$'T%̩81f*P1lT7!y΅ؖQ@5dgy܌#=M'YPQDTyBWRޣHv +V@̟rАe {dG9zyݕ&JKҥ it,fnҸ aMN,t>M~ ب3d>#^u552|κ<:GH GP +a>?F_H5nw~?XIL : ,PԱ8$ߋ,N R WOoAUoA;z>/xkn{ʃ#rSr7-~ ; ??545PSmjvi~~g(}vrQ|U+ux #Im7r4ka hR8#̅&*8tLCNmD' b Iϵ'łQR=T2~_SbXfoqJeF, +ף^ ^YECKn[Xi2|,('BYM^QO\(Nu]Ka[Z !!%gEebpPaUu`rna}ZJr>r0D( ZEyӦR/ U)54=#H,wW>OXqQ:XP(;֥0d0穃F + +ϟףX/F.4Oa)[PVy\rs.}g2J +'l- +lS/`VRNbTztT3i@1*<*Mm,h^ +."8 +( +a.: =N_Ԍ.-fH׊KrV.ħ8*g];Brq!5. K mC 'dIU;@, wv}̟&1_ԯ +xG +ۖ),غOChL*İX;*(o2^B1C xICFi@ "q!,=A #OQ V(Vs +ZUyhB=IA=دtP-A:y#sx+s):ɫlr,X(P&_S!Az>/mn`_DVho]-r/]3B9#> WR}q(_~C~@shQuiE h $=|'5i;j%@$fUr%|y:{lBr1Kq*`\o 0!&ȁ?ƀC#o{nc/Lp/ +.o~֥r4O?9Z^;yW?otM-wv KNbvΜs3s.ƮCv!F/Ӌ6A#:͡?IYX4n) +QL0ڋc'r?;ZGj/+I?@Y=YZ +'[N1sM8 +H^5HG1 +PfA~!=3WfZWj?+< +SWɋQ3?pZ64_CqD'yV( +]>'Y2W-}4# ̨Zyڄ4.92 -=3iTfj^7n`|-' T%Z,Z#ј:Nt0 P/m`şA/wj +x_ۤ8  '%f)@  +Vpxn.5[2ibϠESLRĉV\LH0 +"bX t5iL31۟1d~ۘݔECl0M#yy6pԁ]֨?) +Gʆ|B/y\-y%htr3͛#ҫS*B3dWh);r#xʓA.E2ޡG4(% ]!}Nş|}^^|/__ ?_\k:QC2H z]8~[R<-A)XZ:`T`ϾQR 0u׮j qF: 9bBb8uɐ ]gS1JHZ"⇧ +ب`9`3淺#M>B^Q:yoT03)%!43< ބ'HʻFRjnĭVG@ytu:@ ] _N {gXCAޫG]YB8pK`6:6uP(mODj]p?_G:X +$9-=*Fo/RQ\D*/~A`l>5봴L^u3v 0#Ǥ,n[ +`Kӄ`A(|/d e)? Nΰ9W5x+S .EP L4%,LH=(+ėhZDŽB=[ +eo˾*.~E`^Uj8 奞s@掶Jw5j#n N2&:  +ϋ; +Kj`2( +5YRG_FcnCgnV;CTn6L9Oΐ:3/}E}*)K49،!,'Va3'ޙSh`il +ָ`xM{,͆.6$DJK=uj4,0k"PYz,R ??o_G +t o*?ժ]'WB4sH +`^ +j4Kh@9~ +C +/A=pE}}}{`ma KV,dgܘHN`/c݆AqAO}L3*FˮܗKL Rf@JTcA 7b3Tn-ؽ?3i\ +8N"06 +ꣵ56y(+E?NyV?#R_23@;rNe 4ӱ>H3>A rUR #\xJW~J%|.4j1LjYma?mnftᘼ(*6̉+,2rT' +!Q:&E XfqDZc.௨Z戽-9A!lU=o +6b~H XO Ƨ/@ +X 旒?J^5KR`)of^z6I^o[j4i +eB'CIFӷ]v h&@ٽ<%Y^9ReQq=NI#dƊf$sUA 0VNdOwCjnURkG.F~D(a,2 +m9ɟ8Q -#,x_ˁw\7H{4, : %o_# /7r7H{$#ֿ>+ ۯ"f 8Q`@ H/Fe8{LY;4Z6x +灴t~.[we ]`Z\ULJgN%!M| WI]T9Bb51=ث] D}V(00ß @j| J +T.uKR$>EW)1UY.5%#WX3–c-%ǗaA)a?2f]9@0&x,!spT~cl8!Я B^F]Q(>E<5ZlC8phkx۽'E4ȼ,h)Dv@.v+Rƅo$ |s +lͺhGׄy;A£ZK+y]Ny7s9ivhm[5@bvұ +D}+: +?- CͅaTq yU'ҠtfS1@~T껈I٦5>[٪)ZZ!@r8'r+:Q1oT|A"FTm9p-^j,}<*NjsY)]D.{'[&n;lRxP=Ceh|"4L2&[Q?gd +Grހ/\Ogc ={1~Σ4xς QOk+ve:nuܶv9O\) +ZKVġ <`+SSN:`A;nߒR#w^w\CD +XaSFo ?׃GH-, eFթk)d3FQl7TgZ@<@\D JIh51 +`]BBiwZp:8{?T1)' + ¦4K" CĘ(<TKeꂻ{>D +ؐiynPl + +MȦ$`Uq"XPTo>49te+[GrHN.mHE<6`n2vhTlǩ/_NpݧhOפ`+VA߀A~'\ %) +a,p +b +vtYxW0HvY)Zsyڐ4o4K-}W| +z.K +h6Y m*ɕ3&l1aTn1SwD yf1'ϹQ +6Ǝ +xc^m{%TǮ~6)eWA?t RPQx{Pd,txŅ@8ı͡bҁ*B(^.:mֻ 4Ά=?%A +8}K௻E\xU@h(\uZ7˺֫ +I@hL{:G {a +0S*.߮5dX8_L/A,+]CLT  +UȪ51(̋mc _援`-RVgէx<UQ͗\ Gӝ]c#<1T+r + Ռ0zG +hf +@v{d˭4-ٍH=w ? oh7$9 +۲^kzAѐs:EɰOr7%i'%uXe^!u{zWsmrޥǘުt݄/ ' + J@Î>O.TQ)HPxoI]"2IUǩ1-L\eFpr1&a >M@xCntF +rQ FPC9 Ew! vZ$T}RȆ^Fx"prn@ +W"6S4@pd؟ 0Q0[ɵs) J +]olunzM#>$C(6qm:R}9#T)@o@3 +n*C8Ib9Ւ~ HTcu4M矱 '!Gg0o%M OT%K?^IbIJe&>F HGV`1P3h1E+-v)3A$")r&ԝ@ ]o +(emRL`<";jrWazUephRKf- ےyS>/F8gƄ14 +ȪT݆yNHq0 :۲?@;F kG3vm*=rFytq[fa4//>t" +#Xr}gV#ݩ#)1-]`'mg,$(XsV)lWh{D!hB~g "qLS.h2 + +$Ώ"9PlJoInrt6 lآ3"ma8;B97$cr Eijx׹MbrGZ>MSm ̇cLYwJӮ"؆)ٜh¾Z~$hx]T9ч\bWiRyi +E(% +{^~v1LPcHJyY,#"̪ +'(>uK; ԠOek +(ߛ: +Jjb902Z `1@}`c.4X3S 7΁⻁)%IeCf4Z-ų Vwߡ + #y%HGX| +s cȽzv$!PB:zȫ +@_#uq!^,8QPnK:c& wV:܊<"{+ 缃4jsj05`)` m0K^St:PdTY;4VwY ~>. +/6qIom·^kfj!ʫ o^ݝh ~2 UFK>4ȹa +SHZbQp'En&3ސѽoJL(+HaJqH^bXU"rf_I1$¯R44fXH0; K + 7#p^H&X؀Km޽a{3L<'y\Bz3G +}8*FUsraZn?gr- +vTSiKEx^9&͇!\34Q5|(;_ d +V,e[aB'EʝqRz j[TbZ_-ߧ*wp[^H&?,8Zī\ܢ8miDS1GeԮR=2MeQ +tM8g2JJ]c%=QnO2OOT-U+(4r:j BCl ȝkSxЍ Jk [NQc0O+60Ej!oe?dIe8QO:{? 폸 OtL>(:E +A^<f^}~J p)ʈt +0CE"eP"Jfo?ۮ3CzJyMa^9|J)σ6q9wq PƁPWbC)hZ$#2JP +2D#+›/Iih=יZk㙣ʄl])6 f% +VJ˫r +% +mNBmRTk + +.uk1@-yǵ_wQld. +N9zbA9 +3]A%uNa2w)TP*<(GV?rna +K-T`s) J!ARk  + WZ(1ɁtDd}<|@VX})h\ ZsQG{畂YC[HZHz#,5[H8Ƹ50_uR@p#Ԭvr-wB̯aReJҜ3lyB_ +B˃UK\1!G\FJK!:ɔWģ~7͔ԃC$w`: +#(f2g G9ΖEiw{p*kxV"z>5> _Q}A@\YeR\!ā}Y@ +y?07cF-y~7a(u=cq&;0b`­8&|}Zj5eK +Vs  dG +MR9q!PIDz[He&x9 ]y 2Wup\(5lEN91c= +M_Kٽ衰/_# +."īms}̩g xVWqf|E <VP`u֠NסO{+ES(Q2 +"޾{QeQ\^W+;FW% +Ӝ(Q4r}PR8gݜgJZu +šocs_ +Uo3DZ_"  +* +o;Ѐ}i[sI)Fv }4Wjl[y6J.kbp`%:~7Ȉ+m4nXׇ(j6@aWEWO?=Y +x\o%@~G;d8g gS! +QyCk~.ohwnYrxM5VSp`H% s}#rp%Z[,N\@N czL +}\n@_`k X.lcR!kuIZ )m J + +MUElb8SR!PƝT\ VF[ +KJ +ͩF +|@%j"xZ"E䂙r <?4#٭]#YsO&eS|+xJbCA'e}!+雁OF +.ci3 skXy1ߣI : .xL 7W E!{XS aLݘ@Gi`%exBm}}9ӮGqfH n6g,#gQ`bkqVV:h׽n ptl"a' Q K}hb b ?\Ti_  #k =-5zUya;#4 8.B/Q_YR<[Gy|4*~fvݺ2uS~zdeYi/"սRYAs&7_u"h3: ^ֻ:uԯk#ޥue~!.5UDuxfd k rC&(ZPG'.y/pD8Š+ڳ]j#$nNBm-xK |SLe#?皥;bZ7xJ&H? +WxT +gH ?)6&X2DQѣސƳ=oP3ny@XjZ`4p +@ +E֩8.(|*٠Z،z]ⴸ([ 04IBY۟jKUAϏ&˧Hl!צ܄(w]J  +m8<"pQcžR`m7&ϺsA!r_ *0ew9{ +К 'xb9 + ^^ + NSo?!MHf1̽aZL*5o"y +g\.x$A +DAQYҌz n +G8=i/G'!b¦] +FiO@qU@(w*tKQm0ghC~\z4q#]rىp%l_k70s8D{K>T*}r?SqR*ּrX "hw* +|MHy +Wjv( q@-`^Ȉ.nj;,".P=] +B?ѿm+6b,v|tīy$Z df\{99k*e+n]J\3R~y}+qȃ;q+Gx̨Sxۉ`phM" YڧwMLw +cR@;qF +bC2԰e|=7wP7D4,/a]ؕ:Ot+0 @3BQ,&? ?KG5Gò{tꅧ4We:i 5 Z(6>6hX})lAZEjD1)%^= ūneHj X؀a $Sc*x#mE5q/KP1ۦ-}~_&jA LWC e4}ZQ +P?#ڃ(R׻n  zM6#$IC wZ+v($ȁ6\ +Tg5Eq&NୄIClEz_NAW䷃yrBDڌ[gҪ5FAcXǃJ۾OU +  +̳}& 7\{m vfu$g~6cNķa'&0FOTE);\!C;r9e^IUZ=༁Gҙy{fqTo,)3nqu4х=u,.G\AX +! +~*. +~9F0 c67ѦnuA9Tpa˄zr,2ªzdž&5.پf _OT|6/Aq]Ph.pr*ˢ2J4Z +qf"[ϭDe V^^,VYԝyzTti +bX4zh#LFzbm F$~4o%@aK9&ph|/9'}C޳* +@ ?p TcA!fė +WoC[!-%VVX +w?筢h/_Z\gGCy>ēf-GQhnɎ4JYekU(T+sן!tpR-oiDȜG eYU +6;8mvX!'劣4\=Uk)s0,[(:-* +xR^ݒܷ#])hŝ j|%9߈_B \jmyo?+ nj)۫_ZXX[Ȝ1Xkr&Q?k2ǖ4RPK =Rth:/Bಐs΃ +~BH9u˷vd +ZMޡU V&%GmGi) UZ +T]eDݸJ=W|R՝Fu5G!t%Ǟ%"3s]&GP"( (,*KZ Nq(Md./֓s-1r6HK9RBVcJ 8HtЮQ!uڍab!U.#;}pj$YnpUW^5ڐ"䈏Kg﵈eէ jj?x£Ggcc_ٕr9C<(bAL  +u805hGW?u,c0ESDg}"󗔶`Kȹm74QR\Rh +ІWZIA,vJOƙƁZw;Qk+"B `{;X's-,wEpx*n2‰`&f +&_St" ^|Q:7`m +*!yUVuΜ:7k kXUǓ$sQfV7c +MFauHx,{M +TU6BTԢ0y%7QR +'ߪ3aV)ʦE#dM s K__`9h ʛ0Cl3tl +M9yJu- HG.ߎ2E*|lOtWTGD[OȾ0 -eT.EvfWyܚ +o #9*@w݇p +O +`B>=K:@e5 d pJuXT1As"NO7aT +Z_Fj(ԺHۅ3İMdc5豗]Ҡ^T!ɥ|Hɠ h@K+7H_Dqn?utkFTC +MUKB +_!XMTpx 4@7|ūɢQoɫV"?Ho*9//SDK(d+T|3,")V6fX7p6_ArZ"(<.4Zf>hTr6׺B WRrgػ6z +mC1 ++._JXˁJܰ+VAXpؓI&/p._g_Ěa* zg!vondqX̒ + +ky\W@s +-bvPMf6݈ +<σ7fv.ao” 4qK?jtqXhm`cXߖYRKYefW.vP@j΀t@Lz/` [Un"<[=[xG7#RCGoR5E؜^71*UX!e@7ݠWs.L)7En5.Bc]KNh@nYF.F䭾(ciMQ!8T7=|gǫMh`ͼ!؏tI~b  !_$BEߕkVX xF)Ftn +/6#+jI!HFM)]# GAˉƧ۴R.9B^vp7 xZar +i׶eEIFo =zB =xkVO![*~E)z028M"v fu+o6˘&Hobaw Il r>Ӻ>"^T _SUr'GnCfH <"C +bׂ-UmSA +"Py_c`kxZ?=.wA>b!G4!%Hrq7 4˽:aljZg7Aǥ쾑PSǣ yȟ +`!d)r 06Z$0pOI`^E +@␅{$xBnEd@̟lSآV1UV]C&%tЀaaw)2w9+.P7uε^-r7^CX[~r +jh-l䊅O "v0!Wn@l09prA4P +=OF|C@VDŔ>i>s5+=9| +B7 RM1ħo_uR).&Q8\cgɨ1ְR|FNt[lhv᷶RF(>;r^QBl-$vQ{ӖJ$ՐO)лL[1u>*7pRq\6=7RAƪ4ҍ9I(d@ϼ +!/~* С3wba ~ِnEIcHovm.|oWb ]mAY 6T +$iZMgD8S(3T(uA +sX~kxq& +?vѣfEفG K" =  +ԭ_0} #KKwPet: #tLN#t+ +t +@<uE +U +?yp? x9u+ +Ǎ +N7Gf ES.F ++mH +.HbK +zR64ʗMys.Z۫>?ǽ@כ, ᢠHDM=ߌ~rq^JbZ+8٩d,co\0 2)RF|NAf!==:>NMy5_#8F"PCRF + +x}E4((x GPYn!X ,GxZxO^zMH+l.E\s!f&\WPUAA+^ԌAW"8$ʨinmWiHR YuZ_-ayH~]g?EPX迷郃˗owlTrO̙"eIL8 bG +2Ky]G%5zyğ]-;`M + }L%ѨS!"Kk馜Jd $:]5@o{q7e2>?}_ +Λ }̎=";R3CUnƗn2c'0N8dݮ|7_96 +y~~%=gj}lv9 ]^[_G'Bxp1gAX4/9sSqlEL@gr"8hY2<%orʕWEUE\wd +$R +A@ +! +yGG3 NY8.N2̱$97λq +J)GC_sy~<?S͝#𿺺ebbnߤ!^x]ty%V Q +ilz!0{' +̥{6w x7JGݬ~p~E +"/E{ |*ϟn/OJdُ czoPv?k + Ӄ[! !Y3I;E,'u`|<%Q f\#H?am*нXSAgO@eK/`(7 yid/ c"^j,ʥ9.A@рkR;!(w=vWAmVE zZ -ˣ* 4gcmۮ:{IJ&ga u]w@n  ++rvI!Tf#d͔ Tf|~=Nψwd%Qı>wF콄(c0D1Ǚ©a>d?,F+3A@8(T XF +d+ Q>89[ļϿwYM:ˬ%N=*v&ToÿڷIPG/UëjNۅ^U"Y0t{%Kۀ7`nSر9/Z_Wg/-wƖ~$#վG[בn??8NV j"Lk2L/P{շG`dŏ?ze]*}j3(VS #S9D +.zr`VsNxt(}8T8,gQ[k&c\2YDC*WлccU50sz8 S\ RD  ,c;/d>j|9q쮏ꙮߞG:?aNA.XoSΫc~2~9Җ}(U2+ EO}0 fA)A؊~ +O.~[ +%dj& N - + C5ɌKpRKS-p=:fdjF4۔J-9ngR;uœ Ew[nəV~j 2<,߆=)-w:ٵXCmK,ݴp{򺹛Zw{*^F^\b=Ycw4wߒUJ=l +] A #@ ̫Q>ׂ) +?s-8g/>d4 *ఈ.(GҢG]xf#B?}k"BD?3{ g>t!o Kf&:zHCb Mt3ihjt Wo |F![xתVsuE U;K>z$މ˷"?Қp΂-Oq5:r2߄ +e8`9 ,'sXU8[j oʑSz" [u"[b`寁 +t,P*2TLxxAZdb+ȟqT=@lDScZ|EC In%ɜpU@лc #z6&F Rך~VGj G `^i/yကck ) T r +/aAU؆/%.dAȷĸI7]*G47cɠ])Mb+]lϽ'V +Gƽ~iMz K3CyacF]2T1Ug,CG(5S +R-D)hudY?հ +4Uu_hjr:R?@^Z=}>O,U|giLc (L+)e +qe*m )j +N.0/LfSy!v\3]dXߚ[7ߙ-[r  +f\0 +cX8l)̟w;/ED߈튾 zOJ + ,K@X EPLS&*(^@/=(N ?-GuεK yds +Å zܵfo/}iR1).J\i͐ +OL@`QWΫz T +3T.BS/'-~4C{`It$ +=ʊrCϟ^$&5F2˲ >?E19JD݇$-" +"v<_-oo%nWUpJ04#u,k`[cP=5p~>H>]{1o[q]їꎾ(P ]>{ +K%M< 2Mp` gySɮ{og?K +ۇW|<_І@񧪣̋zjOר" k)yPJ%.mYrJuz2(=> vߒ%Y+׈.@WllWe +P.Ptt{C_?5vO)qqTl6)$c#D1򯍾Mѷt5 .e%A~M}C?GX)N;`TxkdR)gBZ*``aN߬M#g +벃y8oeN/!}EJ>~'}S3^ 9Dobf{ +4 +dW$.f.He'usHNZ[^.?:W+hc¿j[w +t';9JA7ލ͌'v<؈2 tPoTvpG1Ǟ\Ȧ + +9#4}$d=s*P,)p~t4z ;SXḁ]mWb^җDuIPHQJ\aKHޚj(D5uR݂Iۿ  jh\)t# Ȱ:[Q +)IKlv P[]Bڟ[ \\%ohQk\QxGWOJ;?|s[ wD?~;I!Z$ + +x  O|4Cw +(ugx)Sg NL +YLr$ەm^AD +à#)Mvk (ކY2? _"̖g:MA$ܷ]F`7?1eskX=>j\є4!{M#QΥ ~2\s +] ʺQV@m(~B@;LpNCG." +vپE .}BW^ +-28("/W?2f Ý=(G%&0K4 +}g9gJ R6 ۚ@ٻ?s-3 R8濥j+d%TVP + + %dPVRnb!59~KӠ_^ + \5?It+|s +A!M(P]%ĊD:@v }~b0_}7я*TF?dŅI +A\tq jK +J /i!)"T9"ښ| +؃@_U/slEJl;ag$ɡ.$xQ 0F +\  +PFgݜb|@)Ti0$b.X' #P:Xߕ +92)j2Ͳ/.xY_>Zrޣe̒K0iSD45S ,@Dgjk7  -r}AvῃbdN~z=p/Oyy3j[!XNa0=wlaj'W9Ft5~(^" `YdU. xNkԉ tHjPs NA V̬mSNr B6 0{^B` ,/A, +Ȅd!k˱[ɼ+{s!e3 +plְ=U_L7aѤ60 |5|4!]K +pè2)+/Ppe +oDu#V(~b +2@fa$/GV%ϗzGWWף_~!\ R +X~Ej + _ gc$qaj0]Ҁfp)5:Yoc䯥 +0H*w_?g F8o -_,3cFX٭8 dgh6ɱ &v_ +ŊkDjkgRnw|PfO;Io5MGj0 RJT8 :`ބF~rBܐRn+.}9:~;̨?B9_ 6_|mU:gLp+sTD seǠ9 8CQHNPnAbk_B7U EYr48XS\7V3A``4& sfsa@s. +-*m$Q_DGOP 0emi);kH\ނrN$'|Ah6(sY|qgBFY1&̼4`(حŞ-@M`ӐG0mZ 3޶6s +T.j*ϓ 4X?S#4 yGV M~gS +WrN3Lh} rFxxvʔ= mB,,ఈ +)$ٳa-^2bσ\(-ipl?0g=qsj:IQN\OxmrRZMA@MS!tS +M86X-ǯ>g1R;MH`jXp <$ +vzP{M龃*^\)^˗8=wʁ=rm*Fi=tM`hP4qU2J_^DqWQ_ba?$BKEkWVVW!F_QzK7Eok +@M"H"~ +3Q~k;Mk%U0&~[?-d!OtiEڈv=.1o80lgac5u㗂 +r~G\dk,|.)"Ӧ ugS֗[o*qJWd"DOVW)@J7V6˦ +Kҏr_eF 0ԉH;/T#DusRAY r䟛 2yQb}} ;/1liր:h`}zb(uV6 /rZо +k4B+!0gFC{8G\zSعg4uGuش!h`8ى=vO +>O?!Wh_baYb, +Oʐ HiN .5z(Wo0QzҬa Jppzjjx-'}ŮKTAZrnd?J +c$Q|+y +=@ `Un4a wdi{J+DT[ջBj@0נaaIfMz9*ϒʢuIpuE{S('=_9!*xz?G=  ZY.>Yp>_D:q'b<#1^]"UU|xQ4% U}HX=1I%-V +H +di+ࡼ^.˺)sQ$Uu쒀|]z +K[k5TBـ[@7n@. +V-ZyY6. axHRa|43 +k> ʔ +(^X#Ba-L~ i J6*QxrQYR&tI " QJ +s2)c-g߭ OmT c|DP#װ)^\z?Jv`5'Փ +˨Jj)#؄3nQ\jz`dB +SCKs +@xxa_DPb& +\g~pF0 };L!_ +Z K +&z$B2xEoۛf|/omqS1M&J2ҟY?㥜Am E:]o|?FqTɌ tLsT$rC +X\)$'娄{*?i@5$0`PR2OCt* 徬8t`h +e%e@3-\܎~ +$p>.uq*csxi5a k z}iY{X!A{> (EKiQ}_i~>W:kbc`ް?ĘL1?ùrեj)4,.R$@S;,RZXI#0n`_%%\@ȫ'AkΫ& +x&WB 0`\jĪj՛'7u^qM9H9.99Sp; R ^VjؚJYrH7?(XW#Nwf¿A2 +[IVKVkkvmd3Y/.sF]% 0kb'J[&'GL|bR JʨD( +u.ƾ3<5'MAŞJ1'3nDFiߒ#h!#QVUgp+i @bqƜfc +#K Dw +ͳ e-PGc2@ܟDž_#`^ r"EЊ a"3;r{SGՋ!E'o]lW R񑜖q@rZ &/R\{ G"/ފ7 +t?Җy#ߟ~f~i2a@ +$ 4S ̿2?U5?wsX5ɔcmM!' ̴8T+f 3[0 +M`a픓?D(1X|!o%BsHq{Ϟa1gR8byS # z&y+ì#8>h #;9 :8 +$^CW ޟy(@i"p" +(#܍2Jz4շhBlo'\5` mB >[o\dP @,S=jR ޽ +o}յN!#"։=9SW QCR?`(;( dw(~ WE.m %V% +8|GFA)P`&i +nȄY1z8{]x?Cxx r@@8箘܋" +쏑|WO\,y<٦SjlV0}~uy}B \ +7K^ +!~׻v5Q~KѲf$o+&2eRp +O +Q(}K}lPZUcd/%7(RE|zn&3(ng^uɥ%=ɋ T *"/ފ7 +=L@ό~ƶ9>Zj P _AS~WCdd#xq]~a _@4g5#}]rs@p7o_V"v _65r+)ɳGP>YJVAblH6K|iwWQEVV +k}! *n~~tQP 5?0eSB6VpaI^GMHnc +rЗ2sCcJz +ߍgueP}'tZ&y3J\vnN*{kmwccc܍1`l~w=9=9INNNNb;.8I$@B@!z.:<~מ|ᚹkٳgֺ̽ ԸIF'U5(8ﻁՇRǛLG(LcmzZ9G +r#r +nRX +68DNL zfF}5S+SqD#4䩉X@{s ei_*=yQESzK" +8_!^+{B +lY?VL%Er!)2+C=4 +2`1mE+((.^d(@ۨE:CbC_Y@\z&xo4&-l_;B +\⊿V:޻ޅ_~# D IY5ykLK9"P>35>}i/ bFCÍ| QP!(':xA,VHoşs!y??0~.ڍe1l;/߬_aW {jP\ P!c?h*@Y@O4CzIX'8<! +~ +2L9~v3===a*T +nx\`v%;cW]2>|?qC&ϔ21M̛e +@4[F +4!M[SiCCg'\~&y8؜|d[ߍBÍF* bd9x2:}4̺]͠a[B_'@zCZ&0ȍ+  +MSu +,\Jߏ6(D ܾ3^="f.Ͱ֤cF(hij:1 "?NP#d}XujRJ&6WV@Ed)bT +ft/W3 }'sI.:bPV=a]_+R Y +UD|>>P/P7t\e;g_L5H8a喳h AaZ'YÿU +`#N +I߮T!#+ل#Hq2ؿ'UnqB*-ۉCwG805F% +`%yJKSƩckMʰ )kc|(ؐ&фŇI@X M6GLz+o]:)*`m)q[GCi]O屗=,m@C"--=˻[tE(%TtW7D51IupX,v˟<էD9hfgˌWE\~J4N%}n?'t +6eq+\Jiv?=_Ǖ +ox\ooSt!1@3AΌ7O:8[_5WL˨oD҇Qϓ0τLL!o2at#Jd4Xc:6xHDGm l;.މhQ\ +%lHz!*GlE)tC:5БlYPsXOHcٿYdO4f "LbX[87?{`3kr1sjYFMal2 +X")'G r% RvВxpqMTKa ++ ;zW?g4 +~Բ?V_-uW`ݤ;LSo`q%w*,0D +8DQG U'u[Aj +/En?*" + +__(ro c.KV5yP``kb/ߍS~ގ5_4NDq1Iy5 +b~ xiPw[sx\ġ]'*Hrw"N`,eÂa/ 735a)I(Vӡ^F9G:q]c,V$' IVa͏p=c|\yP<"sMss]JEJZ)Cjf7Q-П$Ī[8Q8 ` 1<@+@6&x>mW~00؂g/0%X7شlHw +}1/4O_җK⪧=(^E PaUr"m;h?̮ ͷ$^u{c:qȼBzw0~^w-_R?n5B֟.jnǭԫ +{RNauQqC}rԼ='jX[رR#,f$Mx vi/l +И_P'Q|"ĻU&YJ+FD`x{>H/X#G}t@#S'b +T{T]$>dzdKOi0ּa4/'X l"! ""ޓ ^{ +=W ){ zn~cpÅG yQ ISUX/^&B +otG1aRE `7VҶ|Wg + v~\]*9Jm^-H*vI px|Pq̿VIa[ 6aK~C YIBGv%y *x_ 8/KQ[3 O[~?~|pId5rLv0_l% +r^7wIjYqRc~UHBD?}I_n;?Sq&s@x* r0iGko2ay[U7+o;d>w/so:Nu{Ga +!ҧ>DSSb(}NF1fF'K +fEFIF+&QFUfgtiy$z釥S +?iDv1jE%BZ_,`̟*Hrt ʁec6K +`2"{5^LFR3O|M}_a5ʟ@_w o50?(/JK/ݒ| d=}XK,-DĒhK޾{!DMJ?0V`J?4peˁd4%@ϐāȽ<0ШxNsdpH!|ez<90YZO}w/hi%Ĉ@ Rz`pURi'Z@>bydB2*$.AR6R6)`3A`ꣀPؾĀ\ i,Vz +_輸LܤX$a ^R6$-%' +6n :Ã0y׀_ ٩w#y +~8O(*bzo` +2pyW>|𫹈_T'}<@Yb!VSsrD6VПI'ci)_D:44VD +P3)A#ݹ^;^ +xB$02$.Oi)}ě$9t/ahl9JJ +Srrp$,z"Geyl+^`?%f)NΏ\e1q8Gn̡(cX1eSo&6[Js"0'gś4 ɪD;ϴF8APBMq)* H R〤em6q&-f +P[lkm`G`e$غ0U\:្I~T +'d8/7H + ++$@شF: !!ߗx,e+0A@dp7BF43BΓ)!o +M(:Į:&ˍ#8)o0Sq3{EikG(\8 fM_69;,_ZFcO;UnK(sD6tRK'lVZQ v#0`4 ; 8/ݯh%aU** mrEmirW\ݷE 0h +"fG?^%!3$_.È_V3;!Vɋ< Jc0ޛxF d aWgR_Yh^Ƿ^{)@ԋ3R,[ *iݏ@d= +S fպ; V`x~ ,-čCKZoo)@`[@cJ + c $lgS0ô|[P`J + + _PF4dWPMzoMāV//}E[v U%k4dw?+-:tj fGw@ 'IqQaN'P!FY< +F!0r`5_(}ji0\ +a#bVӁJzl&_+ ¿5?)2x}?NGY[x@."gv(QQJq OәSt=L[ic{{0EÂ;/S %1z:β <77PǑ6bXUmng{)edlۉ T ϥ'?U_g-kP撐c1yoԶrsĒEA4†u +*;ڒ;k:O;}1wٲ\J|)ᖫ:Ў>\:*CxփùxbH=ԫ`YDtQ%=2K'x4Bϓ +?̐atLrjna#Qh4p,&GP\ qcH Ԃ(5sႤP)Mqߌ +48ÝHIF:<ɿh㟐 +bO6 +:OH<?|er͚HdV71Ug/{={I|hʡO*s>h8yŝf<U󮖲hO=i]YBLhdp(J +͡d2-2)T|;^dqrHCT94`L3-=A2a1גGr6 + 5dSH3)XTxΎh Pf *s*㖡gmcrKShmUe + KCZ$Y?5f+CN!x?i +C{&F>*dB]Թ0adN/L ,XC.SpQ/uN 1 S ͥRMP\H#u34#Wɀa/ M +xAI_AYZm_UbՁeÂZ BAX#p?iD%ԛDa & vHXi3mʁ[FSm80xz] ^荓OV`R=.Ra:, +@k_x2[1$?hI9dqǢpgC.2:OJMl|ɰ%$mo4$5ҳT23ZtTQ $ ?MWݯɺ>e:&^afDR /घ{sǝw_Vs?ڧ] uko; +ah2W:P/*.ï?yAj_gQ ؐfKoA?XP>0\ +^F:g.P#$$r0 bhRFn:N`TN +1IU \sg3'/J̈%?$5(/)X] iGy>&m*AJ%KXJaIc%=Ue~pE܁~"}r}Oizq`)3Y3NtCݣhm_txpaYZ/A_(QflM/: 7iDچk0\߰q yQ=4zEP@8 +!Ji9)lE/2Ӣkc, W $ZDx m.7cMWRY5 +i#m|BD tl|zеin!4pfmwg_Y%}D5#;JJڟtmE +?\(T +3+0"&[\բ|y" +n+kC4&%+|(q16%D.ѯe8Ǔl~ZeucTSm [i`Wzq9D8ېc ܇FH0aje 75EN<`kj #i` +BAaohK̆VFqq 8?Kļ}fMIDz߯P|!#sVB*&삶F1XCμu3`@μkhV?#,HBԯqrj@&Ãу ?4*k?h?ֲi?mpXC=F.ŞAI'|Ŀޡٯ}3f2r|0 c&ܻ dkh.`R֐X?Od90h)-3<r Ta`J +JKqj!T'g5;=n0w@Dc +ZgZR=W>u +N_N,9ƂrюpJ[7<ʼn-v&*' Ilu ?]Oɀu@w/??܊2}៝Ű >Z9T? Ę!fpb *ju_p"$>#DhHExjj$#ZOuxA}%&zQY`HЌV9 X r~8IGibӻG +? _M"?= Ca[ +RK$8 +X2=RSiN59>jb_d3 Wty_tkh2d% q.7 +GZ\ĠH0/JyJG/,@s +pZW! +@AZG +'=z#=d8%zGMNH)" $>ӗve:<=h׈.,[[ ma +@xA~:Xi =W3+(IVJBٕ !G{Dz+[iW,]!vֈX]nrQHR}*Sz!o0ŭ#3DG} .vEke;ޣ=w%\N`% z V%+1™!k%5{;%`,9X/u¤X^HaP({!0se-V)3gKx-ӯ 7LTdw]THXcu!/[־lnk$./%u7&m--2s^ +HgA0kzFL?7Ǩg6qcRQ + 糴_BRROYk9 +-)'忐 zDf,9&r5,3n󰉉yL1'b呦?}I_nѳxhfH ~Aq_)7> z6fN#183 l[ +D+tfe+Z=V S=_ϾTqO= s[& q + 3?pY0&߃RAAeF !S )~hg//*u߽F4x2nPYKd +fRDMd+, ]*:E"0/Oڟc.@p~cr4ah}tVZ`(~e#AT ~l_FuM ]rsipt|ﯔnUw-nWCԁq"՞87fj؟{889&kTS0_,YaOP?j E6m6%,k~ic]38'K29ACR _(4,~= ߑy^t8zBY[W˱ePC5JyF9'uR oMT[vQhWtb?1HD,I{OBm$Df +nMltf>a/#~ϲ>d22ǫ¡@g +2wd`EJZ}WH$5's(%ɻfiGJ