; docformat = 'rst'
;
; NAME:
;   cgGoogleMapWidget__Define
;
; PURPOSE:
;   This is a compound widget object that obtains a Google static map from Google Maps 
;   and displays it in the program's draw widget window. It can be used either as a 
;   stand-alone program or to create a map image in draw widget in a larger widget program.
;
;******************************************************************************************;
;                                                                                          ;
;  Copyright (c) 2012, by Fanning Software Consulting, Inc. All rights reserved.           ;
;                                                                                          ;
;  Redistribution and use in source and binary forms, with or without                      ;
;  modification, are permitted provided that the following conditions are met:             ;
;                                                                                          ;
;      * Redistributions of source code must retain the above copyright                    ;
;        notice, this list of conditions and the following disclaimer.                     ;
;      * Redistributions in binary form must reproduce the above copyright                 ;
;        notice, this list of conditions and the following disclaimer in the               ;
;        documentation and/or other materials provided with the distribution.              ;
;      * Neither the name of Fanning Software Consulting, Inc. nor the names of its        ;
;        contributors may be used to endorse or promote products derived from this         ;
;        software without specific prior written permission.                               ;
;                                                                                          ;
;  THIS SOFTWARE IS PROVIDED BY FANNING SOFTWARE CONSULTING, INC. ''AS IS'' AND ANY        ;
;  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES    ;
;  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT     ;
;  SHALL FANNING SOFTWARE CONSULTING, INC. BE LIABLE FOR ANY DIRECT, INDIRECT,             ;
;  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED    ;
;  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;         ;
;  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND             ;
;  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT              ;
;  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS           ;
;  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                            ;
;******************************************************************************************;
;
;+
; This is a compound widget object that obtains a Google static map from Google Maps 
; and displays it in the program's draw widget window. It can be used either as a 
; stand-alone program or to create a map image in draw widget in a larger widget program.
; 
; This program implements a subset of the Google Static Map API, which can be found in
; more detail here: https://developers.google.com/maps/documentation/staticmaps/. The 
; program works by building a URL for a map image. A connection to the Internet is 
; required to then request a map image (in JPEG or PNG format) to be returned from
; Goggle Maps. The returned image is then read and loaded into a draw widget window
; of the right size for the returned map image. The default is to delete the image file
; that is created, but user can also set keywords to retain the image that is downloaded
; from Google Maps. Users are able to control button and motion events in the resulting
; draw widget with their own event handler module. A cgMap coordinate object is created
; to establish a map reference coordinate system on top of the returned map image, allowing
; other map information to be drawn on top of the returned map image.
;
; .. image:: cggooglemapwidget.png
; 
; :Categories:
;    Graphics
;    
;    
; :Examples:
;    Used to put two markers on a map of Fort Collins, Colorado, in a stand-alone window::
;        PRO cgGoogleMapWidget_Test
;            marker1 = {cgGOOGLEMAPMARKER, 'normal', 'dodger blue', 'A', Ptr_New(40.600), Ptr_New(-105.100)}
;            marker2 = {cgGOOGLEMAPMARKER, 'normal', 'purple',      'B', Ptr_New(40.605), Ptr_New(-105.105)}
;            googleObject = Obj_New('cgGoogleMapWidget', MARKERS=[marker1, marker2], MAPTYPE='Terrain')
;        END
;       
; :Author:
;    FANNING SOFTWARE CONSULTING::
;       David W. Fanning 
;       1645 Sheely Drive
;       Fort Collins, CO 80526 USA
;       Phone: 970-221-0438
;       E-mail: david@idlcoyote.com
;       Coyote's Guide to IDL Programming: http://www.idlcoyote.com
;
; :History:
;     Change History::
;        Written, 25 June 2012. DWF.
;        Set the RETAIN keyword on the draw widget for UNIX machines. 28 June 2012. DWF.
;        Beefed up and changed error handling when failing to obtain a map from Google Maps. 28 June 2012. DWF.
;        Added NoForwardFix keyword to call to cgMap to allow better drawing of markers. 29 June 2012. DWF.
;        Added the ability to turn markers on or off with VisibleMarkers keyword and property. 29 June 2012. DWF.
;        Added a WID keyword to the GetProperty method to all the user to obtain the Goggle Map 
;            window index number. 29 Aug 2012. DWF.
;            
; :Copyright:
;     Copyright (c) 2012, Fanning Software Consulting, Inc.
;-

;
;
;+
; This is the initialization method of the cgGoogleMapWidget object. 
; 
; :Params:
;    parent: in, optional, type=long
;         The identifier of the parent widget of the draw widget that is going to be 
;         created by the program. If not provided, the program will create its own 
;         top-level base widget as the parent.
;       
; :Keywords:
;     box_axes: in, optional, type=boolean, default=0
;         Set this keyword to draw box axes around the Google Map.
;     button_events: in, optional, type=boolean, default=0
;         Set this keyword to turn button events on for the draw widget in the program.
;     center_latitude: in, optional, type=float, default=40.60
;         The center latitude of the requested Google map. If not provided, the latitude of
;         Fort Collins, Colorado, home of Coyote. Latitudes are only recognized to four
;         decimals values of precision.
;     center_longitude: in, optional, type=float, default=-105.10
;         The center longitude of the requested Google map. If not provided, the longitude of
;         Fort Collins, Colorado, home of Coyote. Longitude are only recognized to four
;         decimals values of precision.
;     event_method: in, optional, type='string'
;         The name of the event handler method (a procedure) for the draw widget. If you use this keyword,
;         you will also need to write this event handler module. It gets sent one positional
;         parameter, the event structure created by the draw widget.
;     event_pro: in, optional, type='string'
;         The name of an external event handler procedure for the draw widget. The event handler
;         procedure gets sent one positional parameter, the event structure created by the draw widget.
;     imagetype: in, optional, type=string, default='png32'
;         The type of image format the Google map should be returned in. The default is
;         a 32-bit full color PNG file. The image types are given in the Google Static Map
;         API documentation and are as follows: png or png8, png32, gif, jpg, and jpg-baseline.
;     keep_image: in, optional, type=boolean, default=0
;         Set this keyword if you wish to save the Google map as an image when the object
;         is destroyed.
;     maptype: in, optional, type=string, default='terrain'
;         Set this keyword to the type of map you wish to retrieve from Google Maps. The
;         choices are listed in the Google Static Map API documentation and are: "roadmap",
;         "terrain", "satellite", and "hybrid".
;     markers: in, optional, type=structure
;         A scalar or array of cgGoogleMapMarker structures. If present, the markers will
;         be requested with the map from Google. The cgGoogleMapMarker structure is defined
;         like this::
;            struct = { cgGOOGLEMAPMARKER, $
;               size: "", $         ; The marker size ("tiny", "small", "mid" or "normal")
;               color: "", $        ; A color name as provided by cgColor.
;               label: "", $        ; A single uppercase character label from the set {A-Z,0-9}.
;               lats: Ptr_New(), $  ; A pointer to one or more latitude values.
;               lons: Ptr_New() }   ; A pointer to one or more longitude values.
;         Note that the user will be responsible for freeing the pointers in the MARKERS
;         structure. This program will not do that.
;     motion_events: in, optional, type=boolean, default=0
;         Set this keyword to turn motion events on for the draw widget in the program.
;     tempDir: in, optional, type=string
;         The directory where the image containing the Google map is written. By default,
;         it is obtained from the environment like this: tempdir = GetEnv('IDL_TMPDIR').
;     visiblemarkers: in, optional, type=boolean, default=1
;         Set this keyword to 0 to temporarily turn off the display of the markers. Normally,
;         markers are drawn (if present), unless this flag is set to 0.
;     xsize: in, optional, type=int, default=600
;         The X size of the program's draw widget. A maximum of 690 if box axes are requested
;         and a maximum of 640 if no box axes are requested. Box axes require a 25 pixel border
;         and the maximum size of a Google Map is 640 pixels.
;     ysize: in, optional, type=int, default=600
;         The Y size of the program's draw widget. A maximum of 690 if box axes are requested
;         and a maximum of 640 if no box axes are requested. Box axes require a 25 pixel border
;         and the maximum size of a Google Map is 640 pixels.
;     zoomlevel: in, optional, type=integer, default=12
;         The zoom level of the requested Google map. Should be an integer between 0 and 21.
;-
FUNCTION cgGoogleMapWidget::INIT, parent, $
    BOX_AXES=box_axes, $
    BUTTON_EVENTS=button_events, $
    CENTER_LATITUDE=centerLat, $
    CENTER_LONGITUDE=centerLon, $
    EVENT_METHOD=event_method, $
    EVENT_PRO=event_pro, $
    IMAGETYPE=imageType, $
    KEEP_IMAGE=keep_image, $
    MAPTYPE=maptype, $
    MARKERS=markers, $
    MOTION_EVENTS=motion_events, $
    TEMPDIR=tempdir, $
    VISIBLEMARKERS=visiblemarkers, $
    XSIZE=xsize, $
    YSIZE=ysize, $
    ZOOMLEVEL=zoomlevel
    
    Compile_Opt idl2
    
    Catch, theError
    IF theError NE 0 THEN BEGIN
       Catch, /CANCEL
       void = cgErrorMsg()
       RETURN, 0
    ENDIF
    
    ; Check keyword parameters.
    box_axes = Keyword_Set(box_axes)
    maxWindowSize = box_axes ? 690 : 640
    button_events = Keyword_Set(button_events)
    IF N_Elements(centerLat) EQ 0 THEN centerLat = 40.600
    IF N_Elements(centerLon) EQ 0 THEN centerLon = -105.1
    IF N_Elements(event_method) EQ 0 THEN event_method = ""
    IF N_Elements(event_pro) EQ 0 THEN event_pro = ""
    IF N_Elements(imageType) EQ 0 THEN imageType = 'png32' ELSE imageType = StrLowCase(imageType)
    keep_image = Keyword_Set(keep_image)
    IF N_Elements(mapType) EQ 0 THEN mapType = 'terrain' ELSE mapType = StrLowCase(mapType)
    motion_events = Keyword_Set(motion_events)
    IF N_Elements(tempdir) EQ 0 THEN tempdir = GetEnv('IDL_TMPDIR')
    IF N_Elements(visibleMarkers) EQ 0 THEN visibleMarkers = 1
    IF N_Elements(xsize) EQ 0 THEN xsize = 600 ELSE xsize = xsize < maxWindowSize
    IF N_Elements(ysize) EQ 0 THEN ysize = 600 ELSE ysize = ysize < maxWindowSize
    IF N_Elements(zoomlevel) EQ 0 THEN zoomlevel = 12 ELSE zoomlevel = 0 > zoomLevel < 21
    map_xsize = box_axes ? (xsize-50) : xsize
    map_ysize = box_axes ? (ysize-50) : ysize
    
    ; Make sure the temporary directory exists.
    IF ~File_Test(tempDir, /DIRECTORY) THEN File_MkDir, tempDir    

    ; Add a random number generator.
    self.random = Obj_New('RandomNumberGenerator')
    
    ; Use the random number generator to produce a filename for the program.
    filerootName = 'google_map_' + self.random->GetRandomDigits(4)
    CASE imageType OF
       'png': filename = filerootName + '.png'
       'png8': filename = filerootName + '.png'
       'png32': filename = filerootName + '.png'
       'jpg': filename = filerootName + '.jpg'
       'jpg-baseline': filename = filerootName + '.jpg'
       'gif': filename = filerootName + '.jpg'
       ELSE: Message, 'Unknown image type for Google Map: ' + imageType
    ENDCASE
    filename = Filepath(Root_Dir=tempDir, filename)
    
    ; If there is no parent, then create a top-level base widget to display your self in.
    IF N_Elements(parent) EQ 0 THEN BEGIN
       createparent = 1
       parent = Widget_Base(Title='Google Map Widget', COLUMN=1, /BASE_ALIGN_CENTER)
    ENDIF ELSE createparent = 0
    retain = (StrUpCase(!Version.OS_Family) EQ 'UNIX') ? 2 : 1
    drawID = Widget_Draw(parent, XSIZE=xsize, YSIZE=ysize, $
        NOTIFY_REALIZE='cgGoogleMapWidget_Notify_Realize', $
        UVALUE={object:self, method:'Notify_Realize'}, $
        BUTTON_EVENTS=button_events, MOTION_EVENTS=motion_events, $
        EVENT_PRO='cgGoogleMapWidget_Events', RETAIN=retain)
    IF createparent THEN BEGIN
        buttonBase = Widget_Base(parent, ROW=1)
        void = Widget_Button(buttonBase, Value='Zoom In', UVALUE={object:self, method:'ZOOM_IN'})
        void = Widget_Button(buttonBase, Value='Zoom Out', UVALUE={object:self, method:'ZOOM_OUT'})
        menuID = Widget_Button(buttonBase, Value='Map Type...', /MENU)
        void = Widget_Button(menuID, Value='Terrain', UNAME='TERRAIN', $
            UVALUE={object:self, method:'MAP_TYPE'})
        void = Widget_Button(menuID, Value='Satellite', UNAME='SATELLITE', $
            UVALUE={object:self, method:'MAP_TYPE'})
        void = Widget_Button(menuID, Value='Roadmap', UNAME='ROADMAP', $
            UVALUE={object:self, method:'MAP_TYPE'})
        void = Widget_Button(menuID, Value='Hybrid', UNAME='HYBRID', $
            UVALUE={object:self, method:'MAP_TYPE'})
     ENDIF
    
    ; Load the object.
    self.box_axes = box_axes
    self.centerLat = centerLat
    self.centerLon = centerLon
    self.tlb = parent
    self.drawID = drawID
    self.event_method = event_method
    self.event_pro = event_pro
    self.filename = filename
    self.keep_image = keep_image
    self.imageType = imageType
    self.map_xsize = map_xsize
    self.map_ysize = map_ysize
    self.mapType = mapType
    IF box_axes THEN BEGIN
       self.map_position = [25./xsize, 25./ysize, (xsize-25.)/xsize, (ysize-25.)/ysize]
    ENDIF ELSE BEGIN
       self.map_position = [0., 0., 1., 1.]
    ENDELSE
    self.visiblemarkers = Keyword_Set(visibleMarkers)
    self.xsize = xsize
    self.ysize = ysize
    self.tempDir = tempDir
    IF N_Elements(markers) EQ 0 $
        THEN self.markers = Ptr_New(/ALLOCATE_HEAP) $
        ELSE self.markers = Ptr_New(markers)
    self.zoomlevel = zoomlevel
    
    ; Create a map coordinate object for navigating the Google Map.
    self.mapCoord = self -> GetMapCoord()
    
    ; Realize the widget if you created the parent.
    IF createparent THEN Widget_Control, parent, /REALIZE, SET_UVALUE=self
    
    ; Start the program if you created the parent.
    IF createparent THEN BEGIN
       XManager, 'cgGoogleMapWidget', parent, /No_Block, $
          Cleanup='cgGoogleMapWidget_Cleanup', Event_Handler='cgGoogleMapWidget_Events'
    ENDIF
    
    RETURN, 1
END


;+
; The clean-up method for the object. When the object is destroyed, 
; this method will free the object's pointers and objects. If you
; wanted to save the map image file, this is where you do it.
;-
PRO cgGoogleMapWidget::CLEANUP
    Obj_Destroy, self.mapCoord
    Obj_Destroy, self.random
    Ptr_Free, self.markers
    Ptr_Free, self.mapImage
    IF self.keep_image THEN BEGIN
        filename = cgPickFile(/Write, File=File_BaseName(self.filename), $
           Title='Save Google Map Image As...')
        IF filename NE "" THEN BEGIN
           File_Move, self.filename, filename 
        ENDIF ELSE BEGIN
           File_Delete, self.filename
        ENDELSE
    ENDIF
    IF Obj_Valid(self.tlb) THEN Widget_Control, self.tlb, /Destroy
END


;+
; This method creates a cgMap map coordinate object for georeferencing the
; map image returned by Google maps. Use this object to establish a geocoordinate
; reference rectangle for drawing on top of the map image.
;-
PRO cgGoogleMapWidget::CreateMapCoordObject

    Compile_Opt idl2
    
    Catch, theError
    IF theError NE 0 THEN BEGIN
       Catch, /CANCEL
       void = cgErrorMsg()
       Obj_Destroy, mapCoord
       RETURN
    ENDIF
    
    ; Create a map object for navigating the map. Mercator projection, WGS-84 datum.
    mapCoord = Obj_New('cgMap', 105, Datum=8)
  
    ; The metersPerPixel value is determined by using 6371000. meters for the
    ; radius of the Earth, calculating the number of meters per deg lon, then
    ; the number of pixels per deg lon (256/360 for zoom level 0), then figuring
    ; in the zoom level.
    metersPerPixel = cgGoogle_MetersPerPixel(self.zoomlevel)
    xy = mapCoord -> Forward(self.centerLon, self.centerLat)
    xrange = [xy[0] - ((self.map_xsize/2.0)*metersPerPixel), xy[0] + ((self.map_xsize/2.0)*metersPerPixel)]
    yrange = [xy[1] - ((self.map_ysize/2.0)*metersPerPixel), xy[1] + ((self.map_ysize/2.0)*metersPerPixel)]
    mapCoord -> SetProperty, XRANGE=xrange, YRANGE=yrange, POSITION=self.map_position
    
    ; If there is current a map coordinate object, destroy it first.
    IF Obj_Valid(self.mapCoord) THEN Obj_Destroy, self.mapCoord
    self.mapCoord = mapCoord
 
END



;+
; The purpose of this method is obtain the map from Google as an image and display
; it in the draw widget window.
; 
; :Keywords:
;     success: out, optional, type=boolean
;        On return, if set to 1 a map image was successfully obtained from Google. Otherwise, 0.
;-
PRO cgGoogleMapWidget::Draw, SUCCESS=success

    Compile_Opt idl2
    
    Catch, theError
    IF theError NE 0 THEN BEGIN
       Catch, /CANCEL
       void = Dialog_Message('There was a problem in obtaining a map from Google Maps. See Command Log for details')
       Print, !Error_State.Msg
       IF Obj_Valid(netObject) THEN Obj_Destroy, netObject
       IF (!D.Flags AND 256) NE 0 THEN WSet, self.wid
       cgErase
       cgText, 0.05, 0.05, /Normal, 'Google Map Image is Unavailable', FONT=0
       success = 0
       RETURN
    ENDIF
    
    ; Assume success
    success = 1
    
    ; Going to take some time!
    Widget_Control, /Hourglass
    
    ; Basic string for obtaining a Google Static Map.
    googleString = 'http://maps.googleapis.com/maps/api/staticmap?center='

    ; Add the center latitude and longitude
    googleString = googleString + String(self.centerLat,format='(F0.4)') + ',' + $
       String(self.centerLon,format='(F0.4)')
       
    ; Add the zoom level.
    googleString = googleString + '&zoom=' + StrTrim(self.zoomLevel,2)
    
    ; Add the size of the image.
    googleString = googleString + '&size=' + StrTrim(self.map_xsize,2) + 'x' + StrTrim(self.map_ysize,2)
    
    ; Add the map type.
    googleString = googleString + '&maptype=' + StrTrim(StrLowCase(self.mapType),2) 
    
    ; Add sensor information.
    googleString = googleString + '&sensor=false
    
    ; Add image format information.
    googleString = googleString + '&format=' + StrTrim(StrLowCase(self.imageType),2)
    
    ; Are there markers that have to be dealt with?
    numMarkers = N_Elements(*self.markers)   
    IF (numMarkers GT 0) && self.visibleMarkers THEN BEGIN
       markerStr = ""
       FOR j=0,numMarkers-1 DO BEGIN
       
           ; This marker.
           thisMarker = (*self.markers)[j]
           
           ; Default values.
           IF thisMarker.size EQ "" THEN markerSize = 'normal' ELSE markerSize = thisMarker.size
           IF thisMarker.color EQ "" THEN markerColor = 'red' ELSE markerColor = thisMarker.color
           
           ; Colors have to be done in a different way.
           triple = cgColor(markerColor, /Triple)
           color = String(triple[0], Format='(Z2.2)') +  String(triple[1], Format='(Z2.2)') +  String(triple[2], Format='(Z2.2)')
               markerStr = markerStr + "&markers=size:" + StrLowCase(markerSize) + "%7C"
           markerStr = markerStr + "color:0x" + color
           
           ; Need a label?
           IF thisMarker.label NE "" THEN markerStr = markerStr + "%7C" + 'label:' + StrUpCase(thisMarker.label)
           
           ; Make a list of lats and lons.
           lats = *thisMarker.lats
           lons = *thisMarker.lons
           FOR k=0,N_Elements(lats)-1 DO BEGIN
               markerStr = markerStr + "%7C" + Strtrim(lats[k],2) + ',' + StrTrim(lons[k],2)
           ENDFOR
       ENDFOR
       
       ; Add the marker string to the Google string.
       googleString = googleString + markerStr
       
    ENDIF
    
    ; Create an IDL object to connect to the Internet with a URL.
    netObject = Obj_New('IDLnetURL')
    returnName = netObject -> Get(URL=googleString, FILENAME=self.filename)
    Obj_Destroy, netObject
    
    ; Read the image and display it in the draw widget window.
    IF N_Elements(returnName) NE 0 THEN BEGIN
        mapImage = Read_Image(self.filename, r, g, b)
        IF ~self.keep_image THEN File_Delete, self.filename
        IF N_Elements(r) NE 0 THEN TVLCT, r, g, b
        IF (!D.Flags AND 256) NE 0 THEN WSet, self.wid
        cgImage, mapImage, Position=self.map_position, KEEP_ASPECT=1, /Erase, Background='white'
        
        ; Do you need box axes?
        IF self.box_axes THEN BEGIN
            mapCoord = self -> GetMapCoord(/UPDATE)
            cgMap_Grid, /Box_Axes, MAP=mapCoord
        ENDIF
        
       
        ; Store the image
        IF ~Ptr_Valid(self.mapImage) THEN BEGIN
          self.mapImage = Ptr_New(mapImage, /No_Copy) 
        ENDIF ELSE *self.mapImage = mapImage
    ENDIF ELSE BEGIN
        IF (!D.Flags AND 256) NE 0 THEN WSet, self.wid
        success = 0
        cgErase
        cgText, 0.05, 0.05, /Normal, 'Google Map Image is Unavailable', FONT=0
    ENDELSE
       
    Widget_Control, Hourglass=0
END


;+
; The purpose of this method is handle draw widget events. 
; 
;-
PRO cgGoogleMapWidget::DrawWidgetEvents, event

    Compile_Opt idl2
    
    Catch, theError
    IF theError NE 0 THEN BEGIN
       Catch, /CANCEL
       void = cgErrorMsg()
       RETURN
    ENDIF
        
    ; Need to call a method or procedure?
    IF self.event_method NE "" THEN Call_Method, self.event_method, self, event
    IF self.event_pro NE "" THEN Call_Procedure, self.event_pro, event
    
END

;+
; This method returns the map coordinate object that sets up the georeferencing
; coordinate system (in projected meter space) for drawing on top of the map image.
; 
; :Keywords:
;     update: in, optional, type=boolean, default=0
;         Set this keyword to make sure a new map coordinate object is created.
;-
FUNCTION cgGoogleMapWidget::GetMapCoord, UPDATE=update

    Compile_Opt idl2
    
    Catch, theError
    IF theError NE 0 THEN BEGIN
       Catch, /CANCEL
       void = cgErrorMsg()
       RETURN, Obj_New()
    ENDIF
    
    ; If updating, we will need a new map coordinate object.
    IF Keyword_Set(update) THEN Obj_Destroy, self.mapCoord
    
    ; If the map coordinate is not valid, then create one.
    IF ~Obj_Valid(self.mapCoord) THEN self -> CreateMapCoordObject
    
    ; Return the current map coordinate object.
    RETURN, self.mapCoord
    
END

;+
; The properties of the object are retrieved with this method.
;
; :Keywords:
;     center_latitude: out, optional, type=float
;         The center latitude of the requested Google map. 
;     center_longitude: out, optional, type=float
;         The center longitude of the requested Google map. 
;     event_method: out, optional, type='string'
;         The name of the event handler method for the draw widget. 
;     filename: out, optional, type='string'
;         The name of the file where the map image is stored.
;     imagetype: out, optional, type=string
;         The type of image format the Google map should be returned in. 
;     mapcoord: out, optional, type=object
;         The map coordinate object. Another way to obtain the map coordinate object is
;         to use the GetMapCoord method.
;     mapimage: out, optional, type=bytarr
;         The image variable containing the Goggle map. The size and dimensions
;         of the image depend upon what was retrieved from Google.
;     mapposition: out, optional, type=fltarr
;         The position of the map in the display window.
;     maptype: out, optional, type=boolean
;         The type of Google map requested by the program.
;     xsize: out, optional, type=int
;         The X size of the program's draw widget.
;     ysize: out, optional, type=int
;         The Y size of the program's draw widget.
;     zoomlevel: out, optional, type=integer
;         The zoom level of the requested Google map. 
;-
PRO cgGoogleMapWidget::GetProperty, $
    CENTER_LATITUDE=centerLat, $
    CENTER_LONGITUDE=centerLon, $
    EVENT_METHOD=event_method, $
    FILENAME=filename, $
    MAPIMAGE=mapimage, $
    IMAGETYPE=imageType, $
    MAPPOSITION=map_position, $
    MAPCOORD=mapCoord, $
    MAPTYPE=maptype, $
    MARKERS=markers, $
    WID=wid, $
    XSIZE=xsize, $
    YSIZE=ysize, $
    ZOOMLEVEL=zoomlevel

    Compile_Opt idl2
    
    Catch, theError
    IF theError NE 0 THEN BEGIN
       Catch, /CANCEL
       void = cgErrorMsg()
       RETURN
    ENDIF
    
    IF Arg_Present(centerLat) THEN centerLat = self.centerLat
    IF Arg_Present(centerLon) THEN centerLon = self.centerLon
    IF Arg_Present(filename) THEN filename = self.filename
    IF Arg_Present(imageType) THEN imageType = self.imageType
    IF Arg_Present(event_method) THEN event_method = self.event_method
    IF Arg_Present(mapCoord) THEN mapCoord = self.mapCoord
    IF Arg_Present(mapImage) THEN mapImage = *self.mapImage
    IF Arg_Present(map_position) THEN map_position = self.map_position
    IF Arg_Present(maptype) THEN maptype = self.maptype
    IF Arg_Present(markers) THEN IF Ptr_Valid(self.markers) THEN markers = *self.markers
    IF Arg_Present(wid) THEN wid = self.wid
    IF Arg_Present(xsize) THEN xsize = self.xsize
    IF Arg_Present(ysize) THEN ysize = self.ysize
    IF Arg_Present(zoomlevel) THEN zoomlevel = self.zoomlevel
    
END



;+
; The purpose of this method is to display a map with a particular map type.
; 
; :Params:
;    event: in, required, type=varies
;       The event structure passed to this event handler method from which the map
;       type can be obtained. Or, the map type itself, passed as a string.
;-
PRO cgGoogleMapWidget::Map_Type, event

    Compile_Opt idl2
    
    Catch, theError
    IF theError NE 0 THEN BEGIN
       Catch, /CANCEL
       void = cgErrorMsg()
       RETURN
    ENDIF

    IF Size(event, /TNAME) EQ 'STRING' THEN BEGIN
       mapType = event
    ENDIF ELSE BEGIN
       mapType = Widget_Info(event.id, /UNAME)
    ENDELSE
    mapType = StrLowCase(mapType)
    
    validTypes = ['roadmap', 'terrain', 'satellite', 'hybrid']
    index = Where(validTypes EQ mapType, count)
    IF count EQ 0 THEN Message, 'Invalid map type: ' + StrUpCase(mapType)
    
    ; Store it.
    self.mapType = mapType
    
    ; Draw the new map.
    self -> Draw
       
END


;+
; The purpose of this method is to draw the initial map plot in the draw widget.
; 
;-
PRO cgGoogleMapWidget::Notify_Realize

   Compile_Opt idl2
   
   ; Standard error handling.
   Catch, theError
   IF theError NE 0 THEN BEGIN
      Catch, /Cancel
      void = cgErrorMsg()
      RETURN
   ENDIF

    ; Get the window index number of this draw widget.
    Widget_Control, self.drawID, Get_Value=wid
    self.wid = wid
    
    ; Set the draw widget up to handle events.
   Widget_Control, self.drawID, Set_UValue={object:self, method:'DrawWidgetEvents'}
    
     ; Draw the initial map image.
    self -> Draw
    
END


;+
; The purpose of this method is to set some of the object's properties. If
; you wish to retrive a new map after updating the object properties, be sure
; to set the DRAW keyword.
;
; :Keywords:
;     center_latitude: in, optional, type=float, default=40.60
;         The center latitude of the requested Google map. If not provided, the latitude of
;         Fort Collins, Colorado, home of Coyote. Latitudes are only recognized to four
;         decimals values of precision.
;     center_longitude: in, optional, type=float, default=-105.10
;         The center longitude of the requested Google map. If not provided, the longitude of
;         Fort Collins, Colorado, home of Coyote. Longitude are only recognized to four
;         decimals values of precision.
;     draw: in, optional, type=boolean, default=0
;         Set this keyword if you want to immediate retrieve and display a new map with the
;         updated properties.
;     event_method: in, optional, type='string'
;         The name of the event handler method for the draw widget. If you use this keyword,
;         you will also need to write this event handler module. It gets sent one positional
;         parameter, the event structure created by the draw widget.
;     event_pro: in, optional, type='string'
;         The name of an external event handler procedure for the draw widget. The event handler
;         procedure gets sent one positional parameter, the event structure created by the draw widget.
;     imagetype: in, optional, type=string, default='png32'
;         The type of image format the Google map should be returned in. The default is
;         a 32-bit full color PNG file. The image types are given in the Google Static Map
;         API documentation and are as follows: png or png8, png32, gif, jpg, and jpg-baseline.
;     maptype: in, optional, type=string, default='terrain'
;         Set this keyword to the type of map you wish to retrieve from Google Maps. The
;         choices are listed in the Google Static Map API documentation and are: "roadmap",
;         "terrain", "satellite", and "hybrid".
;     markers: in, optional, type=structure
;         A scalar or array of cgGoogleMapMarker structures. If present, the markers will
;         be requested with the map from Google. The GoogleMapMarker structure is defined
;         like this::
;            struct = { cgGOOGLEMAPMARKER, $
;               size: "", $         ; The marker size ("tiny", "small", "mid" or "normal")
;               color: "", $        ; A color name as provided by cgColor.
;               label: "", $        ; A single uppercase character label from the set {A-Z,0-9}.
;               lats: Ptr_New(), $  ; A pointer to one or more latitude values.
;               lons: Ptr_New() }   ; A pointer to one or more longitude values.
;         Note that the user will be responsible for freeing the pointers in the MARKERS
;         structure. This program will not do that.
;     visiblemarkers: in, optional, type=boolean, default=1
;         Set this keyword to 0 to temporarily turn off the display of the markers. Normally,
;         markers are drawn (if present), unless this flag is set to 0.
;     zoomlevel: in, optional, type=integer, default=12
;         The zoom level of the requested Google map. Should be an integer between 0 and 21.

PRO cgGoogleMapWidget::SetProperty, $
    CENTER_LATITUDE=centerLat, $
    CENTER_LONGITUDE=centerLon, $
    DRAW=draw, $
    EVENT_METHOD=event_method, $
    EVENT_PRO=event_pro, $
    IMAGETYPE=imageType, $
    MAPTYPE=maptype, $
    MARKERS=markers, $
    VISIBLEMARKERS=visiblemarkers, $
    ZOOMLEVEL=zoomlevel

    Compile_Opt idl2
    
    Catch, theError
    IF theError NE 0 THEN BEGIN
       Catch, /CANCEL
       void = cgErrorMsg()
       RETURN
    ENDIF
    
    IF N_Elements(centerLat) NE 0 THEN self.centerLat = centerLat
    IF N_Elements(centerLon) NE 0 THEN self.centerLon = centerLon
    IF N_Elements(imageType) NE 0 THEN self.imageType = imageType
    IF N_Elements(event_method) NE 0 THEN self.event_method = event_method
    IF N_Elements(event_pro) NE 0 THEN self.event_pro = event_pro
    IF N_Elements(maptype) NE 0 THEN self.maptype = maptype
    IF N_Elements(markers) NE 0 THEN BEGIN
       IF Ptr_Valid(self.markers) THEN *self.markers = markers ELSE self.markers = Ptr_New(markers)
    ENDIF
    IF N_Elements(visiblemarkers) NE 0 THEN self.visiblemarkers = Keyword_Set(visiblemarkers)
    IF N_Elements(zoomlevel) NE 0 THEN self.zoomlevel = zoomlevel
        
    ; Things have changed. Update the map coordinate object.
    self -> CreateMapCoordObject
    
    ; Need to update the image?
    IF Keyword_Set(draw) THEN self -> Draw
    
END

;+
; The purpose of this method is to make the draw widget window the current window.
;-
PRO cgGoogleMapWidget::SetWindow

    WSet, self.wid
    
END

;+
; The purpose of this method is to zoom into the map by
; increasing the zoom factor.
; 
; :Params:
;    event: in, optional, type=structure
;       The event structure passed to this event handler method. Not used currently.
;-
PRO cgGoogleMapWidget::Zoom_In, event

    Compile_Opt idl2
    
    Catch, theError
    IF theError NE 0 THEN BEGIN
       Catch, /CANCEL
       void = cgErrorMsg()
       RETURN
    ENDIF

    self.zoomlevel = (self.zoomlevel + 1) < 21
    self -> Draw
    self -> CreateMapCoordObject
       
END


;+
; The purpose of this method is to zoom out of the map by
; decreasing the zoom factor.
; 
; :Params:
;    event: in, optional, type=structure
;       The event structure passed to this event handler method. Not used currently.
;-
PRO cgGoogleMapWidget::Zoom_Out, event

    Compile_Opt idl2
    
    Catch, theError
    IF theError NE 0 THEN BEGIN
       Catch, /CANCEL
       void = cgErrorMsg()
       RETURN
    ENDIF

    self.zoomlevel = (self.zoomlevel - 1) > 0
    self -> Draw
    self -> CreateMapCoordObject
       
END


;+
; This is the realize notify routine for the widget. Its function call the
; Realize_Notify method to draw the initial plot in the display window.
; 
; :Params:
;    id: in, required, type=int
;        The widget identifier of the widget that has been realized.
;-
PRO cgGoogleMapWidget_Notify_Realize, id

   Widget_Control, id, Get_UValue=struct
   Call_Method, 'Notify_Realize', struct.object
   
END


;+
; This is the cleanup routine for the widget. Its function is to destroy
; the underlying program object.
; 
; :Params:
;    tlb: in, required, type=int
;        The widget identifier of the parent base widget that just died.
;-
PRO cgGoogleMapWidget_Cleanup, tlb

   Widget_Control, tlb, Get_UValue=self
   Obj_Destroy, self
   
END


;+
; This is the main event handler for the program. All events come here
; to be distributed to the appropriate event handler method according
; to instructions packed into the UVALUE of any widget generating an
; event.
; 
; :Params:
;    event: in, required, type=structure
;        The event structure passed by the window manager.
;-
PRO cgGoogleMapWidget_Events, event

   Compile_Opt idl2
   
   ; Standard error handling.
   Catch, theError
   IF theError NE 0 THEN BEGIN
      Catch, /Cancel
      void = cgErrorMsg()
      RETURN
   ENDIF
   
    Widget_Control, event.id, Get_UValue=instructions
    Call_Method, instructions.method, instructions.object, event

END


;+
; The object class definition for the cgGoogleMapWidget class.
; 
; :Params:
;     class: out, optional, type=structure
;        The object class definition as a structure. Occasionally, comes in handy.
;-
PRO cgGoogleMapWidget__Define, class

    class = { cgGoogleMapWidget, $
              box_axes: 0B, $          ; A flag that indicates box axes should be drawn on the image.
              centerLat: 0.0D, $       ; The center latitude of the Google map.
              centerLon: 0.0D, $       ; The center longitude of the Google map.
              drawID: 0L, $            ; The draw widget identifier.
              event_method: "", $      ; The event method of the draw widget.
              event_pro: "", $         ; An external event handler procedure for the draw widget.
              filename: "", $          ; The name of the image file containing the map after download.
              keep_image: 0B, $        ; Set this flag to save the Google map as an image.
              imageType: "", $         ; The type of image to download (png, jpeg, etc.).
              mapImage: Ptr_New(), $   ; The Goggle map as an image variable.
              map_position: FltArr(4), $ ; The position of the map in the draw widget.
              map_xsize: 0L, $         ; The X size of the map.
              map_ysize: 0L, $         ; The Y size of the map.
              mapType: "", $           ; The type of Google map (terrain, roadmap, satellite, etc.).
              mapCoord: Obj_New(), $   ; A map coordinate object for drawing on top of the map.
              markers: Ptr_New(), $    ; Markers that Google puts on the map.
              random: Obj_New(), $     ; A random number generator object.
              tlb: 0L, $               ; The parent of this widget-object.
              tempDir: "", $           ; The directory where the map is downloaded as an image.
              visibleMarkers: 0B, $    ; A flag that indicates if the markers are shown or not.
              wid: 0L, $               ; The window index number of the draw widget.
              xsize: 0L, $             ; The X size of the draw widget. No larger than 640 pixels.
              ysize: 0L, $             ; The Y size of the draw widget. No larger than 640 pixels.
              zoomlevel: 0 $           ; The zoom level of the Google Map (0 to 21).
            }
             
END