From 458893c56fc72038f260995fc463da1adf27db4e Mon Sep 17 00:00:00 2001
From: "(no author)" <(no author)@8a9318a1-56ba-4d59-b755-99d26321be01>
Date: Thu, 20 Jun 2013 16:46:19 +0000
Subject: [PATCH] added mapped figure plotting and plotting geotiffs
 (un-mapped); simplified how some data availability and correctness checks are
 done

git-svn-id: https://svn.ssec.wisc.edu/repos/glance/trunk@231 8a9318a1-56ba-4d59-b755-99d26321be01
---
 pyglance/glance/gui_figuremanager.py | 309 +++++++++++++++++++++------
 1 file changed, 248 insertions(+), 61 deletions(-)

diff --git a/pyglance/glance/gui_figuremanager.py b/pyglance/glance/gui_figuremanager.py
index f66de76..2cc9143 100644
--- a/pyglance/glance/gui_figuremanager.py
+++ b/pyglance/glance/gui_figuremanager.py
@@ -18,6 +18,8 @@ import matplotlib.cm     as cm
 import matplotlib.pyplot as plt
 import matplotlib.colors as colors
 
+from mpl_toolkits.basemap import Basemap
+
 import logging
 import numpy as np
 
@@ -25,6 +27,7 @@ import glance.data      as dataobjects
 import glance.figures   as figures
 import glance.gui_model as model
 from   glance.gui_constants import *
+from   glance.plotcreatefns import select_projection
 
 LOG = logging.getLogger(__name__)
 
@@ -33,7 +36,7 @@ temp_dict = {'blue': [(0.0, 0.58333333333333326, 0.58333333333333326), (0.11, 0.
 DESAT_MAP = matplotlib.colors.LinearSegmentedColormap('colormap', temp_dict, 1024)
 
 # colormaps that are available in the GUI
-# TODO, if this changes the list of colormap names in the constants module needs to be kept up
+# if this changes the list of colormap names in the constants module needs to be kept up
 AVAILABLE_COLORMAPS = {CM_RAINBOW:       cm.jet,
                        CM_RAINBOW_REV:   cm.jet_r,
                        CM_RAINBOW_DESAT: DESAT_MAP,
@@ -41,6 +44,35 @@ AVAILABLE_COLORMAPS = {CM_RAINBOW:       cm.jet,
                        CM_GRAY_REV:      cm.bone_r,
                        CM_SPECTRAL:      cm.spectral}
 
+# whether or not the plot can be drawn on a map
+CAN_BE_MAPPED = {
+                    ORIGINAL_A      : True,
+                    ORIGINAL_B      : True,
+                    ABS_DIFF        : True,
+                    RAW_DIFF        : True,
+                    HISTOGRAM_A     : False,
+                    HISTOGRAM_B     : False,
+                    HISTOGRAM       : False,
+                    MISMATCH        : True,
+                    SCATTER         : False,
+                    HEX_PLOT        : False,
+                }
+
+# which data sets the plot needs
+NEEDED_DATA_PER_PLOT = \
+                {
+                    ORIGINAL_A      : set([A_CONST]),
+                    ORIGINAL_B      : set([         B_CONST]),
+                    ABS_DIFF        : set([A_CONST, B_CONST]),
+                    RAW_DIFF        : set([A_CONST, B_CONST]),
+                    HISTOGRAM_A     : set([A_CONST]),
+                    HISTOGRAM_B     : set([         B_CONST]),
+                    HISTOGRAM       : set([A_CONST, B_CONST]),
+                    MISMATCH        : set([A_CONST, B_CONST]),
+                    SCATTER         : set([A_CONST, B_CONST]),
+                    HEX_PLOT        : set([A_CONST, B_CONST]),
+                }
+
 class GlanceGUIFigures (object) :
     """
     This class handles creating figures for the glance gui.
@@ -70,7 +102,7 @@ class GlanceGUIFigures (object) :
         if objectToRegister not in self.errorHandlers :
             self.errorHandlers.append(objectToRegister)
     
-    def _getVariableInformation (self, filePrefix, variableName=None) :
+    def _getVariableInformation (self, filePrefix, variableName=None, doCorrections=True) :
         """
         Pull the name, data, and units for the variable currently selected in the given file prefix
         """
@@ -78,7 +110,7 @@ class GlanceGUIFigures (object) :
         if varNameToUse is None :
             varNameToUse = self.dataModel.getVariableName(filePrefix) # get the currently selected variable
         
-        dataObject           = self.dataModel.getVariableData(filePrefix, varNameToUse,  doCorrections=True)
+        dataObject           = self.dataModel.getVariableData(filePrefix, varNameToUse,  doCorrections=doCorrections)
         unitsText            = self.dataModel.getUnitsText   (filePrefix, varNameToUse)
         
         if dataObject is not None :
@@ -95,15 +127,40 @@ class GlanceGUIFigures (object) :
         
         # only load the data if it will be needed for the plot
         if ( self.dataModel.getShouldShowOriginalPlotsInSameRange() or
-             ((imageType == ORIGINAL_A)  and (filePrefix == "A") or
-              (imageType == ORIGINAL_B)  and (filePrefix == "B") or
-              (imageType == HISTOGRAM_A) and (filePrefix == "A") or
-              (imageType == HISTOGRAM_B) and (filePrefix == "B") or
-              (imageType in COMPARISON_IMAGES))) :
-            varName, dataObject, unitsText = self._getVariableInformation(filePrefix)
+             ( filePrefix in NEEDED_DATA_PER_PLOT[imageType] ) ) :
+            
+            shouldUseRGBVersion = self.dataModel.getDoPlotAsRGB(filePrefix) and ( (imageType == ORIGINAL_A) or (imageType == ORIGINAL_B) )
+            varName, dataObject, unitsText = self._getVariableInformation(filePrefix) if not shouldUseRGBVersion else self._makeRGBdata(filePrefix)
         
         return varName, dataObject, unitsText
     
+    def _makeRGBdata (self, filePrefix) :
+        """
+        build an RGB or RGBA version of the data
+        """
+        
+        # get the red, green, and blue data
+        canGetData = self.dataModel.makeSureVariablesAreAvailable(filePrefix, [RED_VAR_NAME, GREEN_VAR_NAME, BLUE_VAR_NAME])
+        if not canGetData : # if the basic rgb data doesn't exist, stop now
+            "", None, ""
+        _, rDataObj, _ = self._getVariableInformation(filePrefix, variableName=RED_VAR_NAME,   doCorrections=False)
+        _, gDataObj, _ = self._getVariableInformation(filePrefix, variableName=GREEN_VAR_NAME, doCorrections=False)
+        _, bDataObj, _ = self._getVariableInformation(filePrefix, variableName=BLUE_VAR_NAME,  doCorrections=False)
+        
+        # if possible get alpha data
+        _ = self.dataModel.makeSureVariablesAreAvailable(filePrefix, [ALPHA_VAR_NAME]) # we need to make sure the model loads the data, but it's optional
+        _, aDataObj, _ = self._getVariableInformation(filePrefix, variableName=ALPHA_VAR_NAME, doCorrections=False)
+        
+        # build the finished rgb set
+        rawData = [rDataObj.data, gDataObj.data, bDataObj.data] if aDataObj is None else [rDataObj.data, gDataObj.data, bDataObj.data, aDataObj.data]
+        rawData = np.rot90(np.fliplr(np.transpose(np.array(rawData))))
+        # now that the data is in the right shape/orientation make the data object
+        newDataObj = dataobjects.DataObject(rawData, fillValue=rDataObj.fill_value) # TODO, need to fix the fill values if they differ
+        newDataObj.self_analysis()
+        
+        # return varName, dataObject, unitsText
+        return "rgb data", newDataObj, ""
+    
     def _buildDiffInfoObjectSmart (self, imageType, dataObjectA, dataObjectB, varNameA, varNameB,
                                    epsilon_value=None, epsilon_percent=None) :
         """
@@ -126,7 +183,7 @@ class GlanceGUIFigures (object) :
         
         return diffObject
     
-    def _load_and_analyse_lonlat (self, listOfFilePrefixes=["A", "B"], lonNames=None, latNames=None, stopIfComparisonFails=False) :
+    def _load_and_analyse_lonlat (self, listOfFilePrefixes=[A_CONST, B_CONST], lonNames=None, latNames=None, stopIfComparisonFails=False) :
         """
         load information on the longidue and latitude,
         if there are multiple file prefixes given:
@@ -149,9 +206,8 @@ class GlanceGUIFigures (object) :
             
             # get information on the lon/lat from the current file
             currentLonObj, currentLatObj, currentLonRange, currentLatRange = self._load_lonlat(filePrefix, lonNames[filePrefix], latNames[filePrefix])
-            
-            # TODO, this will currently crash if there's a problem, we don't really want that
-            assert currentLonObj.data.shape == currentLatObj.data.shape
+            currentLonObj.self_analysis()
+            currentLatObj.self_analysis()
             
             # expand our lon/lat ranges if we need to
             if lonRange is None :
@@ -165,12 +221,19 @@ class GlanceGUIFigures (object) :
                 latRange[0] = min(currentLatRange[0], latRange[0])
                 latRange[1] = max(currentLatRange[1], latRange[1])
             
+            # we can't use longitude and latitude that don't match in size
+            if currentLonObj.data.shape != currentLatObj.data.shape :
+                raise ValueError ("Longitude and Latitude for file " + filePrefix + " are different shapes." +
+                                  "\nCannot match differently shaped navigation data.")
+            
             # compare this file to whatever other data we have
             for filePrefixToCompare in lonlatData.keys() :
                 lonToCompare, latToCompare = lonlatData[filePrefixToCompare]
-                # TODO, this is going to crash if there's a problem, we don't really want that
-                assert lonToCompare.data.shape == currentLatObj.data.shape
-                assert lonToCompare.data.shape == currentLonObj.data.shape
+                # make sure the files are the same shape
+                if (currentLonObj.data.shape != lonToCompare.data.shape) :
+                    raise ValueError ("Navigation data for file " + filePrefix +
+                                      " is a different shape than that for file " + filePrefixToCompare + "." +
+                                      "\nCannot match differently shaped navigation data.")
             
             # add this data to the list of lonlat data
             lonlatData[filePrefix] = [currentLonObj, currentLatObj]
@@ -184,14 +247,64 @@ class GlanceGUIFigures (object) :
         present in both
         """
         
-        _, lonObject, _ = self._getVariableInformation(filePrefix, lonName)
-        _, latObject, _ = self._getVariableInformation(filePrefix, latName)
+        _, lonObject, _ = self._getVariableInformation(filePrefix, lonName, doCorrections=False)
+        _, latObject, _ = self._getVariableInformation(filePrefix, latName, doCorrections=False)
         
         lonRange = [lonObject.get_min(), lonObject.get_max()]
         latRange = [latObject.get_min(), latObject.get_max()]
         
         return lonObject, latObject, lonRange, latRange
     
+    def _find_common_lonlat (self, lonlatData, doUnion=False) :
+        """
+        given lonlatData like that created by _load_and_analyse_lonlat
+        find a common set of longitude and latitude
+        
+        If doUnion is True, create a set that contains valid
+        longitudes and latitudes in as many places as possible.
+        Navigation data will be chosen preferentially based on
+        the sorting order of the keys in lonlatData.
+        If doUnion is False, the intersection of the data will
+        be produced instead (using the first data set by key
+        order and masking by data placement in later sets).
+        """
+        
+        commonLon = None
+        commonLat = None
+        validMask = None
+        
+        # look through each of the possible data sets
+        for file_prefix in sorted(lonlatData.keys()) :
+            tempLonObj, tempLatObj = lonlatData[file_prefix]
+            if commonLon is None :
+                commonLon = tempLonObj.copy()
+                commonLat = tempLatObj.copy()
+                commonLon.self_analysis()
+                commonLat.self_analysis()
+                validMask = commonLon.masks.valid_mask & commonLat.masks.valid_mask
+            else :
+                tempLonObj.self_analysis()
+                tempLatObj.self_analysis()
+                if doUnion :
+                    newValid = (tempLatObj.masks.valid_mask & tempLonObj.masks.valid_mask) & ~ validMask
+                    commonLon.data[newValid] = tempLonObj.data[newValid]
+                    commonLat.data[newValid] = tempLatObj.data[newValid]
+                    validMask |= newValid
+                else:
+                    newInvalid = ~(tempLatObj.masks.valid_mask & tempLonObj.masks.valid_mask) & validMask
+                    commonLon.data[newInvalid] = commonLon.fill_value
+                    commonLat.data[newInvalid] = commonLat.fill_value
+                    validMask &= ~newInvalid
+        
+        # since we changed the data, rebuild the internal analysis
+        commonLat.self_analysis(re_do_analysis=True)
+        commonLon.self_analysis(re_do_analysis=True)
+        
+        LOG.debug("common lon/lat validMask.shape: " + str(validMask.shape))
+        LOG.debug("common lon/lat sum(validMask):  " + str(sum(validMask)))
+        
+        return commonLon, commonLat, validMask
+    
     def spawnPlot (self) :
         """
         create a matplotlib plot using the current model information
@@ -210,8 +323,8 @@ class GlanceGUIFigures (object) :
         LOG.info ("Preparing variable data for plotting...")
         
         # load the variable data
-        aVarName, aDataObject, aUnitsText = self._getVariableInfoSmart("A", imageType)
-        bVarName, bDataObject, bUnitsText = self._getVariableInfoSmart("B", imageType)
+        aVarName, aDataObject, aUnitsText = self._getVariableInfoSmart(A_CONST, imageType)
+        bVarName, bDataObject, bUnitsText = self._getVariableInfoSmart(B_CONST, imageType)
         # compare the variables 
         diffData = self._buildDiffInfoObjectSmart(imageType,
                                                   aDataObject, bDataObject,
@@ -225,36 +338,76 @@ class GlanceGUIFigures (object) :
             rangeInfo = [min(aDataObject.get_min(), bDataObject.get_min()), max(aDataObject.get_max(), bDataObject.get_max())]
         
         # if the user asked for a mapped plotting format and type of plot that is mapped
-        if ((dataForm == MAPPED_2D) and (imageType != HISTOGRAM) and
-                                        (imageType != HISTOGRAM_A) and
-                                        (imageType != HISTOGRAM_B) and 
-                                        (imageType != model.SCATTER) and
-                                        (imageType != model.HEX_PLOT)) :
-            lonNames = {
-                        "A": self.dataModel.getLongitudeName("A"),
-                        "B": self.dataModel.getLongitudeName("B")
-                        }
-            latNames = {
-                        "A": self.dataModel.getLatitudeName("A"),
-                        "B": self.dataModel.getLatitudeName("B")
-                        }
-            lonlatData, lonRange, latRange = self._load_and_analyse_lonlat(listOfFilePrefixes=["A", "B"],
+        lonlatData     = None
+        basemapObject  = None
+        lonlatWarnings = ""
+        if ((dataForm == MAPPED_2D) and CAN_BE_MAPPED[imageType]) :
+            
+            # get the longitude and latitude information for the files, as needed
+            dataNeeded = list(NEEDED_DATA_PER_PLOT[imageType]) # this is naturally a set, use a list here
+            lonNames = { }
+            latNames = { }
+            for file_const in dataNeeded :
+                lonNames[file_const] = self.dataModel.getLongitudeName(file_const)
+                latNames[file_const] = self.dataModel.getLatitudeName (file_const)
+            lonlatData, lonRange, latRange = self._load_and_analyse_lonlat(listOfFilePrefixes=dataNeeded,
                                                                            lonNames=lonNames, latNames=latNames)
             
             # double check that lon/lat are compatable with the data
-            if aDataObject is not None :
-                assert(lonlatData["A"][0].shape == aDataObject.shape)
-            if bDataObject is not None :
-                assert(lonlatData["B"][0].shape == bDataObject.shape)
-            # make composite valid mask
-            allValidMask = ( lonlatData["A"][0].masks.valid_mask & lonlatData["A"][1].masks.valid_mask &
-                             lonlatData["B"][0].masks.valid_mask & lonlatData["B"][1].masks.valid_mask )
+            if (aDataObject is not None) and (A_CONST in dataNeeded) :
+                if lonlatData[A_CONST][0].data.shape != aDataObject.data.shape :
+                    raise ValueError("Unable to use selected navigation variables for file " + A_CONST +
+                                     "\nbecause they differ in size from the selected data variable for that file.")
+            if (bDataObject is not None) and (B_CONST in dataNeeded) :
+                if lonlatData[B_CONST][0].data.shape != bDataObject.data.shape :
+                    raise ValueError("Unable to use selected navigation variables for file " + B_CONST +
+                                     "\nbecause they differ in size from the selected data variable for that file.")
+            # FUTURE if there were ever more data sets, they'd need to be checked individually or make this more general?
             
-            # build basemap, FUTURE, don't hard code so much of this stuff
-            basemapObject = Basemap(llcrnrlon=lonRange[0], llcrnrlat=latRange[0], urcrnrlon=lonRange[1], urcrnrlat=latRange[1],
-                                    resolution='i', area_thresh=10000, projection="merc")
-            # TODO get all these variables outside the if statement
+            # build basemap and axes,
+            # FUTURE, don't hard code so much of this stuff, let the projection and possibly others be selected
+            # FUTURE, some of this is in graphics.py, but needs to be refactored so I can call it in a different way
+            # FUTURE (may go with the axis finding changes from Graeme)
+            boundingAxes  = [lonRange[0], lonRange[1], latRange[0], latRange[1]]
+            projToUse     = select_projection(boundingAxes)
+            LOG.debug("Selecting projection: " + projToUse)
+            midLat        = (latRange[0] + latRange[1]) / 2.0 # this will fail horribly where we cross discontinious lines
+            midLon        = (lonRange[0] + lonRange[1]) / 2.0 # this will fail horribly where we cross discontinious lines
+            if projToUse is 'ortho' :
+                basemapObject = Basemap(lat_0=midLat, lon_0=midLon, resolution='i', area_thresh=10000., projection=projToUse)
+            else :
+                basemapObject = Basemap(llcrnrlon=lonRange[0], urcrnrlon=lonRange[1],
+                                        llcrnrlat=latRange[0], urcrnrlat=latRange[1],
+                                        lat_1=midLat, lon_0=midLon,
+                                        resolution='i', area_thresh=10000., projection=projToUse)
             
+            # do a rough comparison of the longitude and latitude
+            if (aDataObject is not None) and (bDataObject is not None) :
+                llEpsilon = self.dataModel.getLLEpsilon()
+                lonDiffInfo = dataobjects.DiffInfoObject(lonlatData[A_CONST][0],
+                                                         lonlatData[B_CONST][0],
+                                                         epsilonValue=llEpsilon)
+                latDiffInfo = dataobjects.DiffInfoObject(lonlatData[A_CONST][1],
+                                                         lonlatData[B_CONST][1],
+                                                         epsilonValue=llEpsilon)
+                validA = lonlatData[A_CONST][0].masks.valid_mask & lonlatData[A_CONST][1].masks.valid_mask
+                validB = lonlatData[B_CONST][0].masks.valid_mask & lonlatData[B_CONST][1].masks.valid_mask
+                
+                if sum(validA ^ validB) > 0 :
+                    lonlatWarnings += "Valid areas in the two files do not match.\n"
+                    lonlatWarnings += ("File " + A_CONST + " contains " + str(sum(validA & ~ validB)) +
+                                       " points which are not valid in file " + B_CONST + ".\n")
+                    lonlatWarnings += ("File " + B_CONST + " contains " + str(sum(validB & ~ validA)) +
+                                       " points which are not valid in file " + A_CONST + ".\n")
+                
+                if sum(lonDiffInfo.diff_data_object.masks.outside_epsilon_mask) > 0 :
+                    lonlatWarnings += (str(sum(lonDiffInfo.diff_data_object.masks.outside_epsilon_mask)) +
+                                       " longitude points differed by more than the epsilon of " +
+                                       str(llEpsilon) + " between the two files.\n")
+                if sum(latDiffInfo.diff_data_object.masks.outside_epsilon_mask) > 0 :
+                    lonlatWarnings += (str(sum(latDiffInfo.diff_data_object.masks.outside_epsilon_mask)) +
+                                       " latitude points differed by more than the epsilon of " +
+                                       str(llEpsilon) + " between the two files.\n")
         
         LOG.info("Spawning plot window: " + imageType)
         
@@ -267,27 +420,37 @@ class GlanceGUIFigures (object) :
             # sort out some values based on which of the data sets we're showing
             data_object_to_use = aDataObject if (imageType == ORIGINAL_A) else bDataObject
             var_name_to_use    = aVarName    if (imageType == ORIGINAL_A) else bVarName
-            file_char_to_use   = "A"         if (imageType == ORIGINAL_A) else "B"
+            file_char_to_use   = A_CONST     if (imageType == ORIGINAL_A) else B_CONST
             units_text_to_use  = aUnitsText  if (imageType == ORIGINAL_A) else bUnitsText
             oneD_color_to_use  = 'b'         if (imageType == ORIGINAL_A) else 'c'
             
+            plotAsRGB          = self.dataModel.getDoPlotAsRGB(A_CONST if imageType == ORIGINAL_A else B_CONST)
+            
             # if the data doesn't exist, we can't make this plot
             if data_object_to_use is None :
                 
                 raise ValueError(NO_DATA_MESSAGE)
             
             if dataForm == SIMPLE_2D :
-                tempFigure = figures.create_simple_figure(data_object_to_use.data, var_name_to_use + "\nin File " + file_char_to_use,
-                                                          invalidMask=~data_object_to_use.masks.valid_mask, colorMap=colorMapToUse,
-                                                          colorbarLimits=rangeInfo, units=units_text_to_use)
+                if plotAsRGB :
+                    figures.create_raw_image_plot(data_object_to_use.data, "RGB image in File " + file_char_to_use)
+                else :
+                    tempFigure = figures.create_simple_figure(data_object_to_use.data, var_name_to_use + "\nin File " + file_char_to_use,
+                                                              invalidMask=~data_object_to_use.masks.valid_mask, colorMap=colorMapToUse,
+                                                              colorbarLimits=rangeInfo, units=units_text_to_use)
+                
             elif dataForm == MAPPED_2D :
-                #_, tempLatObj, _ = self._getVariableInformation(file_char_to_use, variableName=self.dataModel.getLatitudeName (file_char_to_use))
-                #_, tempLonObj, _ = self._getVariableInformation(file_char_to_use, variableName=self.dataModel.getLongitudeName(file_char_to_use))
-                # TODO ***
-                #tempFigure = figures.create_mapped_figure(data_object_to_use.data, tempLatObj.data, tempLonObj.data, baseMapInstance, boundingAxes, title,
-                #          invalidMask=None, colorMap=None, tagData=None,
-                #          dataRanges=None, dataRangeNames=None, dataRangeColors=None, units=None, **kwargs)
-                pass
+                tempLonObj = lonlatData[file_char_to_use][0]
+                tempLatObj = lonlatData[file_char_to_use][1]
+                tempValid  = data_object_to_use.masks.valid_mask
+                tempValid  &= tempLonObj.masks.valid_mask
+                tempValid  &= tempLatObj.masks.valid_mask
+                tempFigure = figures.create_mapped_figure(data_object_to_use.data,
+                                                          tempLatObj.data, tempLonObj.data,
+                                                          basemapObject, boundingAxes, 
+                                                          var_name_to_use + "\nin File " + file_char_to_use,
+                                                          invalidMask=~tempValid, colorMap=colorMapToUse,
+                                                          units=units_text_to_use)
                 
             elif dataForm == ONLY_1D :
                 temp = [(data_object_to_use.data, ~data_object_to_use.masks.valid_mask, oneD_color_to_use, None, None, None)]
@@ -300,7 +463,7 @@ class GlanceGUIFigures (object) :
             # Note: histograms don't care about data format requested, they are histogram formatted
             
             # select the things that are file A or B specific
-            file_desc_to_use   = "A"         if (imageType == HISTOGRAM_A) else "B"
+            file_desc_to_use   = A_CONST     if (imageType == HISTOGRAM_A) else B_CONST
             var_name_to_use    = aVarName    if (imageType == HISTOGRAM_A) else bVarName
             data_object_to_use = aDataObject if (imageType == HISTOGRAM_A) else bDataObject
             units_text_to_use  = aUnitsText  if (imageType == HISTOGRAM_A) else bUnitsText
@@ -333,7 +496,16 @@ class GlanceGUIFigures (object) :
                                                               invalidMask=~diffData.diff_data_object.masks.valid_mask,
                                                               colorMap=colorMapToUse, units=aUnitsText)
                 elif dataForm == MAPPED_2D :
-                    pass # TODO
+                    
+                    tempLonObj, tempLatObj, tempValid = self._find_common_lonlat(lonlatData)
+                    tempValid &= diffData.diff_data_object.masks.valid_mask
+                    tempFigure = figures.create_mapped_figure(dataToUse,
+                                                              tempLatObj.data, tempLonObj.data,
+                                                              basemapObject, boundingAxes, 
+                                                              titlePrefix + aVarName,
+                                                              invalidMask=~tempValid, colorMap=colorMapToUse,
+                                                              units=aUnitsText)
+                    
                 elif dataForm == ONLY_1D :
                     tempTitle = titlePrefix + aVarName
                     if aVarName != bVarName :
@@ -346,13 +518,26 @@ class GlanceGUIFigures (object) :
             elif imageType == MISMATCH :
                 
                 mismatchMask = diffData.diff_data_object.masks.mismatch_mask
+                
                 if dataForm == SIMPLE_2D :
                     tempFigure = figures.create_simple_figure(aDataObject.data, "Areas of mismatch data\nin " + aVarName,
                                                               invalidMask=~aDataObject.masks.valid_mask, tagData=mismatchMask,
                                                               colorMap=figures.MEDIUM_GRAY_COLOR_MAP, units=aUnitsText)
-                                                                # TODO, change colormap?
                 elif dataForm == MAPPED_2D :
-                    pass # TODO
+                    
+                    tempLonObj, tempLatObj, tempValid = self._find_common_lonlat(lonlatData, doUnion=True)
+                    tempValid &= (aDataObject.masks.valid_mask | bDataObject.masks.valid_mask)
+                    tempData = aDataObject.copy()
+                    tempMask = bDataObject.masks.valid_mask & ~aDataObject.masks.valid_mask
+                    tempData.data[tempMask] = bDataObject.data[tempMask]
+                    tempFigure = figures.create_mapped_figure(tempData.data,
+                                                              tempLatObj.data, tempLonObj.data,
+                                                              basemapObject, boundingAxes, 
+                                                              "Areas of mismatch data\nin " + aVarName,
+                                                              invalidMask=~tempValid,
+                                                              tagData=mismatchMask,
+                                                              colorMap=figures.MEDIUM_GRAY_COLOR_MAP,
+                                                              units=aUnitsText)
                 elif dataForm == ONLY_1D :
                     
                     temp = [(aDataObject.data, ~aDataObject.masks.valid_mask, 'k', None, mismatchMask, None)]
@@ -396,6 +581,8 @@ class GlanceGUIFigures (object) :
                                                     "File B Value in " + bVarName,
                                                     epsilon=self.dataModel.getEpsilon(),
                                                     units_x=aUnitsText, units_y=bUnitsText)
-            
         
         plt.draw()
+        
+        if lonlatWarnings != "" :
+            raise ValueError(lonlatWarnings)
-- 
GitLab