diff --git a/cave/build/cave/customTargets.xml b/cave/build/cave/customTargets.xml index 612a856854..e3644b19ad 100644 --- a/cave/build/cave/customTargets.xml +++ b/cave/build/cave/customTargets.xml @@ -112,6 +112,10 @@ + + + + diff --git a/cave/build/p2-build.xml b/cave/build/p2-build.xml index 7acd223efc..ffa3684862 100644 --- a/cave/build/p2-build.xml +++ b/cave/build/p2-build.xml @@ -107,6 +107,12 @@ + + + + + @@ -266,7 +272,11 @@ - + + + + diff --git a/cave/build/static/common/cave/etc/gfe/userPython/procedures/MakeHazard.py b/cave/build/static/common/cave/etc/gfe/userPython/procedures/MakeHazard.py new file mode 100644 index 0000000000..8b263bd0ab --- /dev/null +++ b/cave/build/static/common/cave/etc/gfe/userPython/procedures/MakeHazard.py @@ -0,0 +1,307 @@ +# ---------------------------------------------------------------------------- +# This software is in the public domain, furnished "as is", without technical +# support, and with no warranty, express or implied, as to its usefulness for +# any purpose. +# +# MakeHazard.py +# +# SOFTWARE HISTORY +# Date Ticket# Engineer Description +# ------------ ---------- ----------- -------------------------- +# Apr 03,2012 436 randerso Converted to Python procedure to allow some +# level of site customization +# Apr 09,2012 436 randerso Merged RNK's MakeHazards_Elevation procedure +# +# Author: randerso +# ---------------------------------------------------------------------------- + +# The MenuItems list defines the GFE menu item(s) under which the +# Procedure is to appear. +# Possible items are: Populate, Edit, Consistency, Verify, Hazards +MenuItems = ["Hazards"] + + +import SmartScript +import time, string, sys +import HazardUtils +import re +import numpy +import LogStream +import JUtil + +class Procedure (SmartScript.SmartScript): + def __init__(self, dbss): + SmartScript.SmartScript.__init__(self, dbss) + + self._dataManager = dbss + self._afterInit = 0 #flag indicating init is done. + + self._tropicalHaz = ['HU.W','HU.A','HU.S','TR.W','TR.A'] + self._natlBaseETN = 1001 + + + def setUpUI(self): + if sys.modules.has_key("MakeHazardConfig"): + sys.modules.__delitem__("MakeHazardConfig") + import MakeHazardConfig + + args = {} + args['dataManager'] = self._dataManager + args['selectedTimeRange'] = self.selectedTimeRange + args['mapColor'] = MakeHazardConfig.mapColor + args['defaultMapWidth'] = MakeHazardConfig.defaultMapWidth + args['timeScaleEndTime'] = MakeHazardConfig.timeScaleEndTime + args['areaThreshold'] = MakeHazardConfig.areaThreshold + args['defaultHazardType'] = MakeHazardConfig.defaultHazardType + args['mapNames'] = MakeHazardConfig.mapNames + args['hazardDict'] = MakeHazardConfig.hazardDict + args['tcmList'] = MakeHazardConfig.tcmList + args['tropicalHaz'] = self._tropicalHaz + args['natlBaseETN'] = self._natlBaseETN + + if not hasattr(MakeHazardConfig, 'localEffectAreas') or \ + MakeHazardConfig.localEffectAreas is None: + args['localEffectAreas'] = {} + else: + args['localEffectAreas'] = MakeHazardConfig.localEffectAreas + + if not hasattr(MakeHazardConfig, 'localAreaData') or \ + MakeHazardConfig.localAreaData is None: + args['localAreaData'] = {} + else: + args['localAreaData'] = MakeHazardConfig.localAreaData + + # create the Java/SWT dialog and open it + from com.raytheon.viz.gfe.makehazard import MakeHazardDialog + self.__dlg = MakeHazardDialog.createFromPython( + JUtil.pyValToJavaObj(args) + ) + self.__dlg.openFromPython() + + # run the Java/SWT event loop + try: + dismiss = False + while not dismiss: + args = JUtil.javaObjToPyVal(self.__dlg.runFromPython(), converter) + dismiss = True; + # if args is None, then Cancel was pressed + if args is not None: + # dismiss is True if the Run/Dismiss button is pressed, + # false if Run is pressed + dismiss = args["dismiss"] + del args["dismiss"] + + if self.makeHazardGrid(**args) != 1: + dismiss = False + finally: + # close the Java/SWT dialog when Cancelled, Dismissed or exception occurs + self.__dlg.closeFromPython() + + # RJM modified this routine from the HazardUtility file + # returns a Numeric mask where each zone in zoneList is set to 1 + def _makeMask(self, zoneList, hazLocalEffect): + + # RJM had to modify this next line to point to the hazUtils + # for the getGridSize routine. + gridSize = self._hazUtils._getGridSize() + mask = numpy.zeros(gridSize) + eaList = self.editAreaList() + + # Get the elevation from the GUI input. We'll do this by clipping + # of any numerical digits from the local effect. +# elevation_string = re.findall("\d+", hazLocalEffect) +# print "re elevation=", elevation_string, "xxx" +# try: +# elevation = elevation_string[0] +# except: +# elevation = "None" +# print "re elevation=", elevation, "xxx" + for z in zoneList: + print "in _makeMask processing zone ", z + + if z in eaList: + zoneArea = self.getEditArea(z) + zoneMask = self.encodeEditArea(zoneArea) + + # Code added by RJM. This checks to see if the local effect + # area was specified and is a valid edit area. If so, + # make a mask from it, and then do an intersection with + # the zone mask. + if hazLocalEffect in eaList: + print "Masking",z,"with",hazLocalEffect + localEffectArea = self.getEditArea(hazLocalEffect) + localEffectMask = self.encodeEditArea(localEffectArea) + zoneMask = numpy.logical_and(zoneMask, localEffectMask) + + mask = numpy.logical_or(mask, zoneMask) +# else: +# if z in eaList: +# zoneArea = self.getEditArea(z) +# zoneMask = self.encodeEditArea(zoneArea) +# mask = numpy.logical_or(mask, zoneMask) + + return mask + + # Creates the hazard grid based on the dialog input + def makeHazardGrid(self, selectedHazard, timeRange, areaList, segmentNumber, + selectedTimeRange, defaultAreaList, defaultHazard, defaultSegment, + hazLocalEffect): + siteID = self.getSiteID() + usingHazLocalEffect = (hazLocalEffect != 'None') + + if len(areaList) == 0: + editArea = self.getActiveEditArea() + mask = self.encodeEditArea(editArea) + else: + # make the mask based on the list selections + if not usingHazLocalEffect: + mask = self._hazUtils._makeMask(areaList) + else: + mask = self._makeMask(areaList, hazLocalEffect) + + if usingHazLocalEffect: + # get the segment number and filter for valid characters + segNum = segmentNumber + + # get the hazards currently defined as temporary grids + hazParms = self.getHazardParmNames() + + # look through the list of grids and create a list of + # segment numbers (if any) that are already in use + # for the current hazard +# if len(hazParms) == 0: +# self.statusBarMsg("No temporary grids to merge.", "S") +# return 0 + segList = [] + print "selectedHazard=", selectedHazard + selectedPhen = selectedHazard[0:2] + selectedSig = selectedHazard[3] + print "selectedPhen,selectedSig=", selectedPhen, ".", selectedSig + for hazParm in hazParms: + print "hazParm=", hazParm + trList = self._hazUtils._getWEInventory(hazParm) + for tr in trList: + print " tr=", tr, timeRange + intersect_hours = tr.intersection(timeRange).duration() + print " intersect=", intersect_hours + intersect_percent = intersect_hours / timeRange.duration() * 100.0 + print " intersect %=", intersect_percent + phen = hazParm[3:5] + sig = hazParm[5:6] + print "phen,sig=", phen, ".", sig + if len(hazParm) > 6: + if hazParm[6:].isdigit(): + seg = int(hazParm[6:]) + print " seg=", seg + if phen == selectedPhen and sig == selectedSig: + segList.append(seg) + print "appending ", seg + else: + seg = 0 + segList.sort() + +# print "looping through segList" +# for seg in segList: +# print " seg=", seg," elev=", elevation +# if str(elevation) == str(seg): +# print "adding 1 to elevation" +# elevation += 1 +# +# if elevation > 400: +# print "using elevation for segNum" +# segNum = elevation +# # replace the segmentNumber field with the elevation +/- the Above/Below indicator. +# self.__dlg.setSegmentNumber(elevation) +# segmentNumber = str(elevation) +# print "*** segmentNumber=", segmentNumber + + index = string.find(selectedHazard, " ") + if index != -1: + selectedHazard = selectedHazard[0:index] + if len(segmentNumber) > 0: + hazardKey = selectedHazard + ":" + segmentNumber + else: + hazardKey = selectedHazard + + defaultHazKey = "" + if len(defaultSegment) > 0 and defaultHazard is not None: + defaultHazKey = defaultHazard + ":" + defaultSegment + + weName = self._hazUtils._makeTempWEName(hazardKey) + + # if we're modifying, remove the old grid first + if defaultAreaList != [] and hazardKey == defaultHazKey: + self.deleteCmd([weName], self.selectedTimeRange) + + # if we have no selection prevent user from making an empty hazard + if 1 not in mask: + self.statusBarMsg("NO EDIT AREA SELECTED: \n Select area from map or load edit area in GFE!", "S") + return 0 + + self._hazUtils._addHazard(weName, timeRange, hazardKey, mask) + LogStream.logUse("Set: ", weName, + self._hazUtils._printTime(timeRange.startTime().unixTime()), + self._hazUtils._printTime(timeRange.endTime().unixTime()), hazardKey, + self._hazUtils._printAreas(areaList)) + + return 1 + + def getHazardParmNames(self): + # get the list of loaded temporary hazard parms + parms = self.loadedParms() + hazParms = [] + for weName, level, dbID in parms: + if "haz" in weName: + key = self._hazUtils._tempWENameToKey(weName) + index = string.find(key, ":") + if index != -1: + mkey = key[0:index] + segNum = key[index+1:] + else: + mkey = key + segNum = "" + + # append the hazard and a description + parmName = "haz" + key + parmName = string.replace(parmName, ".", "") + parmName = string.replace(parmName, ":", "") + hazParms.append(parmName) + + return hazParms + + def execute(self, timeRange): + #self._hazUtils = HazardUtils.HazardUtils(self._dataManager, self.eaMgr()) + self._hazUtils = HazardUtils.HazardUtils(self._dataManager, None) + # save the selected timeRange + self.selectedTimeRange = timeRange + + self.setToolType("numeric") + + # see if the Hazards WE is loaded in the GFE, if not abort the tool + if not self._hazUtils._hazardsLoaded(): + self.statusBarMsg("Hazards Weather Element must be loaded in " + \ + "the GFE before running MakeHazard", "S") + self.cancel() + + + # always separate the Hazards grid first + self._hazUtils._separateHazardGrids() + + self.setUpUI() + + self._afterInit = 1 #initialization done + + return + +def converter(obj): + import AbsTime + import TimeRange + retVal = None + + objtype = obj.jclassname + if objtype == "java.util.Date": + retVal = AbsTime.AbsTime(obj) + elif objtype == "com.raytheon.uf.common.time.TimeRange": + retVal = TimeRange.TimeRange(obj) + return retVal + diff --git a/cave/build/static/common/cave/etc/gfe/userPython/procedures/MakeHazardConfig.py b/cave/build/static/common/cave/etc/gfe/userPython/procedures/MakeHazardConfig.py deleted file mode 100644 index eb04b5c6ab..0000000000 --- a/cave/build/static/common/cave/etc/gfe/userPython/procedures/MakeHazardConfig.py +++ /dev/null @@ -1,157 +0,0 @@ -## -# This software was developed and / or modified by Raytheon Company, -# pursuant to Contract DG133W-05-CQ-1067 with the US Government. -# -# U.S. EXPORT CONTROLLED TECHNICAL DATA -# This software product contains export-restricted data whose -# export/transfer/disclosure is restricted by U.S. law. Dissemination -# to non-U.S. persons whether in the United States or abroad requires -# an export license or other authorization. -# -# Contractor Name: Raytheon Company -# Contractor Address: 6825 Pine Street, Suite 340 -# Mail Stop B8 -# Omaha, NE 68106 -# 402.291.0100 -# -# See the AWIPS II Master Rights File ("Master Rights File.pdf") for -# further licensing information. -## - -# AWIPS1 imports SmartScript to retrieve the site ID below, but -# since all we need is just that site ID, it makes more sense to -# import DataManager directly to get that rather than all of the -# SmartScript module -# import SmartScript -from com.raytheon.viz.gfe.core import DataManager - -def sortHazardList(dict): - #sorts the entries in the menus in alphabetical order, returns sorted - #dictionary - import VTECTable - for ent in dict.keys(): - values = dict[ent] - # get the descriptive word for this phen/sig - items = [] - for v in values: - desc = VTECTable.VTECTable.get(v,'') - items.append((desc, v)) - items.sort() #sorts by description - #extract out the sorted phen/sig - phensig = [] - for desc, v in items: - phensig.append(v) - dict[ent] = phensig - return dict - - -########################################################### -############## ############### -############## CONFIGURATION SECTION ############### -############## ############### - -# Lists of hazards organized by type in a dictionary -# Set these to value you use for your site. To minimize scrolling, -# change the order so that the most common values your site uses are -# near the front of each list. The key is the menu entry on the -# Make Hazard dialog, the values are the key values for Hazards. -siteID = DataManager.getCurrentInstance().getSiteID() -if siteID == "GUM": - hazardDict = { - 'Hydrology' : ["FF.A", "FA.A"], - 'Fire Weather' : ["FW.A", "FW.W"], - 'Coastal Flood' : ["CF.S", "LS.S", "CF.Y", "CF.W", "CF.A", - "SU.Y", "SU.W", "LS.Y", "LS.W", "LS.A", "RP.S"], - 'Non-Precipitation' : ["AF.W", "AF.Y", "AQ.Y", "AS.O", "AS.Y", "DU.Y", - "DS.W", "EH.W", "EH.A", "EC.W", "EC.A", "FG.Y", "FZ.W", "FZ.A", - "HZ.W", "HZ.A", "ZF.Y", "FR.Y", "HT.Y", "HW.W", "HW.A", - "LW.Y", "SM.Y", "WI.Y"], - 'Marine' : ["MA.S", "MH.W", "MH.Y", "BW.Y", "UP.Y", "MF.Y", - "GL.A", "GL.W", "SE.A", "SE.W", "UP.A", "UP.W", "HF.A", "HF.W", "LO.Y", "SC.Y", "SW.Y", - "RB.Y", "SI.Y", "MS.Y", "SR.A", "SR.W"], - 'Typhoon' : ["TY.A", "TY.W", "TR.A", "TR.W", "HU.S"], - 'Tsunami' : ["TS.A", "TS.W"], - - #'Local' : ["TEST"], #example of adding local hazards - # you can define your own groups of hazards by adding new categories - } -else: - hazardDict = { - 'Winter Weather' : ["BZ.W", "BZ.A", "ZR.Y", - "IS.W", "LE.Y", "LE.W", "LE.A", - "WC.Y", "WC.W", "WC.A", "WS.W", "WS.A", "WW.Y"], - 'Hydrology' : ["FF.A", "FA.A"], - 'Fire Weather' : ["FW.A", "FW.W"], - 'Convective Watches' : ["SV.A", "TO.A"], - 'Coastal Flood' : ["CF.S", "LS.S", "CF.Y", "CF.W", "CF.A", - "SU.Y", "SU.W", "LS.Y", "LS.W", "LS.A", "RP.S"], - 'Non-Precipitation' : ["AF.W", "AF.Y", "AQ.Y", "AS.O", "AS.Y", "DU.Y", - "DS.W", "EH.W", "EH.A", "EC.W", "EC.A", "FG.Y", "FZ.W", "FZ.A", - "HZ.W", "HZ.A", "ZF.Y", "FR.Y", "HT.Y", "HW.W", "HW.A", - "LW.Y", "SM.Y", "WI.Y"], - 'Marine' : ["MA.S", "MH.W", "MH.Y", "BW.Y", "UP.Y", "MF.Y", - "GL.A", "GL.W", "SE.A", "SE.W", "UP.A", "UP.W", "HF.A", "HF.W", "LO.Y", "SC.Y", "SW.Y", - "RB.Y", "SI.Y", "MS.Y", "SR.A", "SR.W"], - 'Tropical Cyclone' : ["HU.W", "HU.A", "HU.S", "TR.W", "TR.A"], - 'Tsunami' : ["TS.A", "TS.W"], - - #'Local' : ["TEST"], #example of adding local hazards - # you can define your own groups of hazards by adding new categories - } - - -# This function sorts the hazards in the hazardDict by description. -# Comment it out if this is not desired. -hazardDict = sortHazardList(hazardDict) - - # Dictionary of map categories and the map names. The "" is - # substituted with your site name. The names of the map must match - # those defined in the ifpServer. The keys in mapNames must match - # the keys in hazardDict. - -mapNames = { - 'Fire Weather' : ["FireWxZones_"], - 'Hydrology' : ["Zones_"], - 'Coastal Flood': ["Zones_"], - 'Convective Watches' : ["Marine_Zones_","FIPS_"], - 'Non-Precipitation' : ["Zones_"], - 'Tropical Cyclone' : ["Offshore_Marine_Zones_", - "Marine_Zones_","Zones_"], - 'Typhoon' : ["Offshore_Marine_Zones_", - "Marine_Zones_","Zones_"], - 'Tsunami' : ["Offshore_Marine_Zones_", - "Marine_Zones_","Zones_"], - 'Winter Weather' : ["Zones_"], - 'Marine' : ["Offshore_Marine_Zones_", - "Marine_Zones_"], - #'Local' : ["Zones_"], #example of adding local class - } - -# The hazard type chosen when MakeHazard opens -defaultHazardType = "Non-Precipitation" - -# this is the color for the selected areas in the map -mapColor = "red" - -# initial map width -defaultMapWidth = 400; - -# the percentage that an area must be covered to default to selected -areaThreshold = 0.10 - -# End time in hours of the time scales -timeScaleEndTime = 96 - -# Define the tropical product used to identify the particular storm -tcmList = [] # Comment out for HLS sites - -# Uncomment line below for Atlantic basin sites -#tcmList = ["TCMAT1", "TCMAT2", "TCMAT3", "TCMAT4", "TCMAT5"] - -# Uncomment line below for EPac basin sites -#tcmList = ["TCMEP1", "TCMEP2", "TCMEP3", "TCMEP4", "TCMEP5"] - -# Uncomment line below for CPac basin sites -#self.tcmList = ["TCMCP1", "TCMCP2", "TCMCP3", "TCMCP4", "TCMCP5"] - -#################### END CONFIGURATION SECTION ################# \ No newline at end of file diff --git a/cave/build/static/common/cave/etc/gfe/userPython/utilities/HazardUtils.py b/cave/build/static/common/cave/etc/gfe/userPython/utilities/HazardUtils.py index 47e2594371..d6d61ed7cd 100644 --- a/cave/build/static/common/cave/etc/gfe/userPython/utilities/HazardUtils.py +++ b/cave/build/static/common/cave/etc/gfe/userPython/utilities/HazardUtils.py @@ -1061,10 +1061,7 @@ class HazardUtils(SmartScript.SmartScript): #print areas, from dictionary def _printAreas(self, areas): - ara = [] - for a in areas.keys(): - if areas[a] == 1: - ara.append(a) + ara = list(areas) ara.sort() return ara diff --git a/cave/build/static/common/cave/etc/gfe/userPython/utilities/MakeHazardConfig.py b/cave/build/static/common/cave/etc/gfe/userPython/utilities/MakeHazardConfig.py new file mode 100644 index 0000000000..ea7e2fa44e --- /dev/null +++ b/cave/build/static/common/cave/etc/gfe/userPython/utilities/MakeHazardConfig.py @@ -0,0 +1,182 @@ +# ---------------------------------------------------------------------------- +# This software is in the public domain, furnished "as is", without technical +# support, and with no warranty, express or implied, as to its usefulness for +# any purpose. +# +# MakeHazard.py +# +# SOFTWARE HISTORY +# Date Ticket# Engineer Description +# ------------ ---------- ----------- -------------------------- +# Jul 10,2012 436 randerso Separated configuration data from the +# MakeHazard procedure +# +# Author: randerso +# ---------------------------------------------------------------------------- + +def sortHazardList(dict): + #sorts the entries in the menus in alphabetical order, returns sorted + #dictionary + import VTECTable + for ent in dict.keys(): + values = dict[ent] + # get the descriptive word for this phen/sig + items = [] + for v in values: + desc = VTECTable.VTECTable.get(v,'') + items.append((desc, v)) + items.sort() #sorts by description + #extract out the sorted phen/sig + phensig = [] + for desc, v in items: + phensig.append(v) + dict[ent] = phensig + + return dict + +# Lists of hazards organized by type in a dictionary +# Set these to value you use for your site. To minimize scrolling, +# change the order so that the most common values your site uses are +# near the front of each list. The key is the menu entry on the +# Make Hazard dialog, the values are the key values for Hazards. + +# Using OrderedDict allows you to control the order in which the +# Hazard Types are displayed in the dialog +# +from collections import OrderedDict +hazardDict = OrderedDict([ + ('Winter Weather', ["BZ.W", "BZ.A", "ZR.Y", + "IS.W", "LE.Y", "LE.W", "LE.A", + "WC.Y", "WC.W", "WC.A", "WS.W", "WS.A", "WW.Y"]), + ('Hydrology', ["FF.A", "FA.A"]), + ('Fire Weather', ["FW.A", "FW.W"]), + ('Convective Watches', ["SV.A", "TO.A"]), + ('Coastal Flood', ["CF.S", "LS.S", "CF.Y", "CF.W", "CF.A", + "SU.Y", "SU.W", "LS.Y", "LS.W", "LS.A", "RP.S"]), + ('Non-Precipitation', ["AF.W", "AF.Y", "AQ.Y", "AS.O", "AS.Y", "DU.Y", + "DS.W", "EH.W", "EH.A", "EC.W", "EC.A", "FG.Y", "FZ.W", "FZ.A", + "HZ.W", "HZ.A", "ZF.Y", "FR.Y", "HT.Y", "HW.W", "HW.A", + "LW.Y", "SM.Y", "WI.Y"]), + ('Marine', ["MA.S", "MH.W", "MH.Y", "BW.Y", "UP.Y", "MF.Y", + "GL.A", "GL.W", "SE.A", "SE.W", "UP.A", "UP.W", "HF.A", "HF.W", "LO.Y", "SC.Y", "SW.Y", + "RB.Y", "SI.Y", "MS.Y", "SR.A", "SR.W"]), + ('Tropical Cyclone', ["HU.W", "HU.A", "HU.S", "TR.W", "TR.A"]), + ('Tsunami', ["TS.A", "TS.W"]), + + # ('Local', ["TEST"]), #example of adding local hazards + # you can define your own groups of hazards by adding new categories + ]) + +# for GUM use comment out the above definition and uncomment the one below + +#hazardDict = OrderedDict([ +# ('Hydrology', ["FF.A", "FA.A"]), +# ('Fire Weather', ["FW.A", "FW.W"]), +# ('Coastal Flood', ["CF.S", "LS.S", "CF.Y", "CF.W", "CF.A", +# "SU.Y", "SU.W", "LS.Y", "LS.W", "LS.A", "RP.S"]), +# ('Non-Precipitation', ["AF.W", "AF.Y", "AQ.Y", "AS.O", "AS.Y", "DU.Y", +# "DS.W", "EH.W", "EH.A", "EC.W", "EC.A", "FG.Y", "FZ.W", "FZ.A", +# "HZ.W", "HZ.A", "ZF.Y", "FR.Y", "HT.Y", "HW.W", "HW.A", +# "LW.Y", "SM.Y", "WI.Y"]), +# ('Marine', ["MA.S", "MH.W", "MH.Y", "BW.Y", "UP.Y", "MF.Y", +# "GL.A", "GL.W", "SE.A", "SE.W", "UP.A", "UP.W", "HF.A", "HF.W", "LO.Y", "SC.Y", "SW.Y", +# "RB.Y", "SI.Y", "MS.Y", "SR.A", "SR.W"]), +# ('Typhoon', ["TY.A", "TY.W", "TR.A", "TR.W", "HU.S"]), +# ('Tsunami', ["TS.A", "TS.W"]), +# +# # ('Local', ["TEST"]), #example of adding local hazards +# # you can define your own groups of hazards by adding new categories +# ]) + + +# This function sorts the hazards in the hazardDict by description. +# Comment it out if this is not desired. +hazardDict = sortHazardList(hazardDict) + + # Dictionary of map categories and the map names. The "" is + # substituted with your site name. The names of the map must match + # those defined in the ifpServer. The keys in mapNames must match + # the keys in hazardDict. + +mapNames = { + 'Fire Weather' : ["FireWxZones_"], + 'Hydrology' : ["Zones_"], + 'Coastal Flood': ["Zones_"], + 'Convective Watches' : ["Marine_Zones_","FIPS_"], + 'Non-Precipitation' : ["Zones_"], + 'Tropical Cyclone' : ["Offshore_Marine_Zones_", + "Marine_Zones_","Zones_"], + 'Typhoon' : ["Offshore_Marine_Zones_", + "Marine_Zones_","Zones_"], + 'Tsunami' : ["Offshore_Marine_Zones_", + "Marine_Zones_","Zones_"], + 'Winter Weather' : ["Zones_"], + 'Marine' : ["Offshore_Marine_Zones_", + "Marine_Zones_"], + #'Local' : ["Zones_"], #example of adding local class + } + +# The defaultHazardType - selected when the tool is first run. This +# must be one of the categories (keys) in the mapNames and hazardDict. +defaultHazardType = "Non-Precipitation" + +# this is the color for the selected areas in the map +mapColor = "red" # color of selected areas + +# initial map width +defaultMapWidth = 400; + +# the percentage that an area must be covered to default to selected +areaThreshold = 0.10 + +# End time in hours of the time scales +timeScaleEndTime = 96 + +# Define the tropical product used to identify the particular storm +tcmList = [] # Comment out for HLS sites + +# Uncomment line below for Atlantic basin sites +#tcmList = ["TCMAT1", "TCMAT2", "TCMAT3", "TCMAT4", "TCMAT5"] + +# Uncomment line below for EPac basin sites +#tcmList = ["TCMEP1", "TCMEP2", "TCMEP3", "TCMEP4", "TCMEP5"] + +# Uncomment line below for CPac basin sites +#tcmList = ["TCMCP1", "TCMCP2", "TCMCP3", "TCMCP4", "TCMCP5"] + +# Dictionary mapping Hazard Types to applicable local effect areas +# that can be intersected with the zone edit areas. +# You should not define localEffectAreas entries for Tropical Cyclone +# or Convective Watches. +localEffectAreas = {} + +#localEffectAreas = { +# 'Winter Weather' : ["Below_1000","Below_1500","Below_2000","Below_2500","Below_3000","Below_3500","Below_4000", +# "Above_1000","Above_1500","Above_2000","Above_2500","Above_3000","Above_3500"], +# } + +# Dictionary associating local Effect Area names with a corresponding +# segment number, display name, and list of zones to be auto-selected +# If you do not wish to auto-select zones you should supply an empty list +# +# The display name allows you to display a "pretty" string in the UI rather +# than the edit area name. If the display name is empty ("") the edit area +# name will be used. +localAreaData = {} + +#localAreaData = { +# "Below_1000" : ( 999, "", []), +# "Below_1500" : (1499, "", []), +# "Below_2000" : (1999, "", []), +# "Below_2500" : (2499, "", []), +# "Below_3000" : (2999, "", []), +# "Below_3500" : (3499, "", []), +# "Below_4000" : (3999, "", []), +# "Above_1000" : (1000, "", []), +# "Above_1500" : (1500, "", []), +# "Above_2000" : (2000, "", []), +# "Above_2500" : (2500, "", []), +# "Above_3000" : (3000, "", []), +# "Above_3500" : (3500, "", []), +# } + diff --git a/cave/build/static/common/cave/etc/ghg/userPython/MakeHazard.py b/cave/build/static/common/cave/etc/ghg/userPython/MakeHazard.py deleted file mode 100644 index 33e037a3f0..0000000000 --- a/cave/build/static/common/cave/etc/ghg/userPython/MakeHazard.py +++ /dev/null @@ -1,126 +0,0 @@ -## -# This software was developed and / or modified by Raytheon Company, -# pursuant to Contract DG133W-05-CQ-1067 with the US Government. -# -# U.S. EXPORT CONTROLLED TECHNICAL DATA -# This software product contains export-restricted data whose -# export/transfer/disclosure is restricted by U.S. law. Dissemination -# to non-U.S. persons whether in the United States or abroad requires -# an export license or other authorization. -# -# Contractor Name: Raytheon Company -# Contractor Address: 6825 Pine Street, Suite 340 -# Mail Stop B8 -# Omaha, NE 68106 -# 402.291.0100 -# -# See the AWIPS II Master Rights File ("Master Rights File.pdf") for -# further licensing information. -## -# ---------------------------------------------------------------------------- -# This software is in the public domain, furnished "as is", without technical -# support, and with no warranty, express or implied, as to its usefulness for -# any purpose. -# -# MakeHazard.py -# -# Author: wdougherty -# ---------------------------------------------------------------------------- -import numpy -import JUtil -import LogStream -import TimeRange -from HazardUtils import HazardUtils -from HazardUtils import ELEMENT -from HazardUtils import MODEL -from HazardUtils import LEVEL -from HazardUtils import SUCCESS -from HazardUtils import FAIL_REDUNDANT - -def makeHazard(hazard, dbss, timeRange, zones, segment, selectedTimeRange, defaultAreaList, defaultHazard, defaultSegment): - hazardUtils = HazardUtils(dbss, None) - # hazard has already been validated - # timeRange has already been validated - - # convert zones to a Python list - if zones is None: - zones = [] - if defaultAreaList is None: - defaultAreaList = [] - - if not isinstance(zones, list): - zones = JUtil.javaStringListToPylist(zones) - if not isinstance(defaultAreaList, list): - defaultAreaList = JUtil.javaStringListToPylist(defaultAreaList) - - # Find the mask that describes the area covered by the hazard - if [] == zones: - ea = hazardUtils.getActiveEditArea() - if ea is None: - mask = None - else: - mask = hazardUtils.encodeEditArea(ea) - else: - mask = hazardUtils._makeMask(zones) - - # Hazards need to be separated for makeHazard's temp hazard grid. - # If the user hasn't already separated them, it's up to us. - if hazardUtils._tempWELoaded(): - pass # Hazards are already separated - else: - hazParm = hazardUtils.getParm(MODEL,ELEMENT,LEVEL) - # If the inventory span is invalid, the hazards grid is empty. - if hazParm.getInventorySpan().isValid(): - if SUCCESS != hazardUtils._separateHazardGrids(): - return False - - # Build the hazard key - hazardKey = hazard - defaultHazardKey = defaultHazard - - if segment is None: - segment = "" - if defaultSegment is None: - defaultSegment = "" - - segment = segment.strip() - defaultSegment = defaultSegment.strip() - - if "" != segment: - hazardKey = hazardKey + ":" + segment - if "" != defaultSegment and defaultHazardKey is not None: - defaultHazardKey = defaultHazardKey + ":" + defaultSegment - - # Create the name of the temp weather element from hazardKey - weName = hazardUtils._makeTempWEName(hazardKey) - - # if we're modifying, remove the old grid first - if defaultAreaList != [] and hazardKey == defaultHazardKey: - hazardUtils.deleteCmd([weName], selectedTimeRange) - - # Don't allow the user to create an empty hazard grid - if not numpy.any(mask): - hazardUtils.statusBarMsg( - "NO EDIT AREA SELECTED: \n Select area from map or load edit area in GFE!", - "S", "GHG Status") - return False - - # Create the temporary hazard grid - hazardUtils._addHazard(weName, timeRange, hazardKey, mask) - - # convert timeRange to Python for logging - timeRange = TimeRange.TimeRange(timeRange) - # log the creation of the temp hazard - LogStream.logEvent("Set: " + weName + " " + - hazardUtils._printTime(timeRange.startTime().unixTime()) + " " + - hazardUtils._printTime(timeRange.endTime().unixTime()) + " " + - hazardKey + " " + str(zones)) - return True - -## -# -def ensureSeparated(dbss): - hazardUtils = HazardUtils(dbss, None) - rtnCode = hazardUtils._separateHazardGrids() - separated = rtnCode in [SUCCESS, FAIL_REDUNDANT] - return separated diff --git a/cave/build/static/common/cave/etc/ghg/userPython/TCMETNDecoder.py b/cave/build/static/common/cave/etc/ghg/userPython/TCMETNDecoder.py deleted file mode 100644 index 0d09aad2bb..0000000000 --- a/cave/build/static/common/cave/etc/ghg/userPython/TCMETNDecoder.py +++ /dev/null @@ -1,250 +0,0 @@ -## -# This software was developed and / or modified by Raytheon Company, -# pursuant to Contract DG133W-05-CQ-1067 with the US Government. -# -# U.S. EXPORT CONTROLLED TECHNICAL DATA -# This software product contains export-restricted data whose -# export/transfer/disclosure is restricted by U.S. law. Dissemination -# to non-U.S. persons whether in the United States or abroad requires -# an export license or other authorization. -# -# Contractor Name: Raytheon Company -# Contractor Address: 6825 Pine Street, Suite 340 -# Mail Stop B8 -# Omaha, NE 68106 -# 402.291.0100 -# -# See the AWIPS II Master Rights File ("Master Rights File.pdf") for -# further licensing information. -## -# ---------------------------------------------------------------------------- - -import string, time -from com.raytheon.uf.common.status import UFStatus -from com.raytheon.uf.common.status import UFStatus_Priority as Priority - - -### New class for fetching ETN from TCM header for tropical hazards -class TCMDecoder: - def __init__(self): - self.pos = 0 - # key words in TCM products from NCEP - self.keyWordDict = {"NATIONAL HURRICANE CENTER" : self.decodeAltFilename, - } - - self.fcstList = [] # a place to store all of the forecasts - - self.text = [] # the text product - - self.currentFcst = {} # the current forecast we are docoding - - self.baseProductTime = 0 - - self.altFilename = "" - - self._handler = UFStatus.getHandler("GFE", 'GFE') - - def stripText(self): - endStr = chr(13) + chr(13) + chr(10) - for i in range(len(self.text)): - self.text[i] = string.replace(self.text[i], endStr, "") - return - - def getFcstList(self): - return self.fcstList - - def getBaseProductTime(self): - return self.baseProductTime - - def getAltInfoFilename(self): - return self.altFilename - - def currentLine(self): - return self.text[self.pos] - - def nextLine(self): - self.pos = self.pos + 1 - if self.pos < len(self.text): - return self.text[self.pos] - else: - return "" - - def monthNum(self, monthStr): - monthList = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", - "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"] - - try: - return monthList.index(monthStr) + 1 - except ValueError: - return 0 - - def convertBaseTime(self, timeStr): - # timeStr format: "HHMM UTC DAY MON DD YYYY" - try: - baseTime = time.strptime(timeStr, "%H%M UTC %a %b %d %Y") - print "baseTime is", baseTime - return baseTime - except ValueError: - return - - - # extract time parts from the str -# strList = string.split(timeStr) -# if len(strList) != 6: -# print "Invalid time string:", timeStr -# print "Format should be of the form HHMM UTC DAY MON DD YYYY" -# return -# -# hour = int(timeStr[0:2]) -# minute = int(timeStr[2:4]) -# monthStr = strList[3] -# month = self.monthNum(monthStr) -# day = int(strList[4]) -# year = int(strList[5]) -# -# # time.mktime returns time in seconds but in local time -# baseTime = time.mktime((year, month, day, hour, minute, 0, 0, 0, 0)) -# print "month is", month -# print "baseTime is", baseTime - -## # Adjust to UTC -## diffTime = time.mktime(time.gmtime()) - time.mktime(time.localtime()) -## print "diffTime is", diffTime - -## # subtract timeZone and round to the nearest hour -## roundedTime = int((baseTime - diffTime) / 3600) * 3600 -## -# return baseTime - - def convert_ddhhmm(self, ddhhmmStr, baseTime): - - # remove the slash if present - ddhhmmStr = string.replace(ddhhmmStr, "/", "") - - if baseTime == 0: - baseTime = time.time() - - # extract the time parts - dayStr = ddhhmmStr[0:2] - hourStr = ddhhmmStr[2:4] - minStr = ddhhmmStr[4:6] - day = int(dayStr) - hour = int(hourStr) - minute = int(minStr) - tupleTime = time.gmtime(baseTime) - year = tupleTime[0] - month = tupleTime[1] - # see if we crossed over to a new month - if tupleTime[2] > day: - month = month + 1 - if month > 12: - month = 1 - year = year + 1 - - newTuple = (year, month, day, hour, minute, tupleTime[5], - tupleTime[6], tupleTime[7], tupleTime[8]) - - secondsTime = time.mktime(newTuple) - # Adjustment to UTC - diffTime = time.mktime(time.gmtime()) - time.mktime(time.localtime()) - return secondsTime - diffTime # subtract timeZone - - def decodeProductTime(self): - # Time of the product found on the next line - timeStr = self.nextLine() - print "the time string is:", timeStr - - # sanity check for the time string - hhmm = timeStr[0:4] - for c in hhmm: - if not c in string.digits: - return - - baseTime = self.convertBaseTime(timeStr) - self.baseProductTime = baseTime - - return - - def decodeAltFilename(self): - nameStr = self.currentLine() - parts = string.split(nameStr) - - self.altFilename = parts[-1] # grab the last string token - return - - def decodeTCMProduct(self, TCMProduct): - self.text = TCMProduct - self.pos = 0 - self.fcstList = [] -## self.defaultEyeDiameter = eyeDiameter - - self.stripText() - while self.pos < len(TCMProduct): - line = self.currentLine() - for k in self.keyWordDict.keys(): - if string.find(line, k) > -1: - self.keyWordDict[k]() - break - self.pos = self.pos + 1 - - # store the last forecast in the list of forecasts - if self.currentFcst != {}: - self.fcstList.append(self.currentFcst) - self.currentFcst = {} # reset - - return -## End TCM decoder class - -### New methods to pull ETN from TCM if grid is not initialized with ETN -def tcmETNforTrop(tcmProduct): - tcmProd = tcmProduct -# print "chosen TCM is", tcmProd - tcmDecoder = TCMDecoder() - TCMProduct = getTextProductFromDB(tcmProd) - if len(TCMProduct) < 3: - msg = tcmProd + " could not be retrieved from the text database." - statusBarMsg(msg, "S") - return None # Just return if no TCM is found. Something's really wrong - else: - tcmDecoder.decodeTCMProduct(TCMProduct) - altFileName = tcmDecoder.getAltInfoFilename() - stormNum = altFileName[2:4] -## print "storm number is", stormNum - nationalBase = "10" - tropicalETN = nationalBase + stormNum -## print "Tropical ETN is: ", tropicalETN -## print "lenth of tropical ETN is:", len(tropicalETN) - return tropicalETN - -def getTextProductFromDB(productID): - from com.raytheon.viz.gfe import Activator - from com.raytheon.viz.gfe.core import DataManager - from com.raytheon.viz.gfe.product import TextDBUtil - - prefStore = Activator.getDefault().getPreferenceStore() - if prefStore.contains("TestVTECDecode"): - testVtec = prefStore.getBoolean("TestVTECDecode") - else: - testVtec = False - gfeMode = (DataManager.getCurrentInstance().getOpMode().name() == "OPERATIONAL") - opMode = testVtec or gfeMode - fullText = TextDBUtil.retrieveProduct(productID, opMode) - textList = fullText.splitlines(True) - return textList - -def statusBarMsg(message, status, category="GFE"): - from com.raytheon.uf.common.status import UFStatus - from com.raytheon.uf.common.status import UFStatus_Priority as Priority - from com.raytheon.viz.gfe import Activator - from com.raytheon.viz.gfe.constants import StatusConstants - - if "A" == status: - importance = Priority.PROBLEM - elif "R" == status: - importance = Priority.EVENTA - elif "U" == status: - importance = Priority.CRITICAL - else: - importance = Priority.SIGNIFICANT - - self._handler.handle(importance,message) \ No newline at end of file diff --git a/cave/com.raytheon.uf.viz.ccfp/src/com/raytheon/uf/viz/ccfp/rsc/CcfpResource.java b/cave/com.raytheon.uf.viz.ccfp/src/com/raytheon/uf/viz/ccfp/rsc/CcfpResource.java index f2bd70a70f..543b7370c5 100644 --- a/cave/com.raytheon.uf.viz.ccfp/src/com/raytheon/uf/viz/ccfp/rsc/CcfpResource.java +++ b/cave/com.raytheon.uf.viz.ccfp/src/com/raytheon/uf/viz/ccfp/rsc/CcfpResource.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; @@ -63,6 +64,7 @@ import com.vividsolutions.jts.geom.Point; * Date Ticket# Engineer Description * ------------ ---------- ----------- -------------------------- * Sep 22, 2009 3072 bsteffen Initial creation + * Aug 23, 2012 1096 njensen Fixed memory leaks * * * @@ -161,8 +163,10 @@ public class CcfpResource extends } private void disposeFrames() { - for (DisplayFrame frame : frames.values()) { - frame.dispose(); + synchronized (frames) { + for (DisplayFrame frame : frames.values()) { + frame.dispose(); + } } } @@ -180,11 +184,15 @@ public class CcfpResource extends @Override public String inspect(ReferencedCoordinate coord) throws VizException { - if (frames.get(this.displayedDataTime) == null) { + DisplayFrame frame = null; + synchronized (frames) { + frame = frames.get(this.displayedDataTime); + } + if (frame == null) { return ""; } StringBuilder res = new StringBuilder(); - for (CcfpRecord record : frames.get(this.displayedDataTime).records) { + for (CcfpRecord record : frame.records) { // Check if we have an area we are rendering if (isPaintingArea(record)) { try { @@ -216,15 +224,20 @@ public class CcfpResource extends */ private void updateRecords(IGraphicsTarget target, PaintProperties paintProps) throws VizException { - DisplayFrame frame = frames.get(this.displayedDataTime); - if (frame == null) { - frame = new DisplayFrame(); - frames.put(this.displayedDataTime, frame); + DisplayFrame frame = null; + synchronized (frames) { + frame = frames.get(this.displayedDataTime); + if (frame == null) { + frame = new DisplayFrame(); + frames.put(this.displayedDataTime, frame); + } } // Add all the new Records - Collection newRecords = unprocessedRecords - .get(this.displayedDataTime); + Collection newRecords = null; + synchronized (unprocessedRecords) { + newRecords = unprocessedRecords.get(this.displayedDataTime); + } for (CcfpRecord record : newRecords) { // If we need to draw anything for this record then keep it if (isPaintingArea(record) || isPaintingMovement(record) @@ -259,14 +272,19 @@ public class CcfpResource extends this.displayedDataTime = paintProps.getDataTime(); // First check to see if we need to process new data - Collection unprocessed = unprocessedRecords - .get(this.displayedDataTime); + Collection unprocessed = null; + synchronized (unprocessedRecords) { + unprocessed = unprocessedRecords.get(this.displayedDataTime); + } if (unprocessed != null && unprocessed.size() > 0) { updateRecords(target, paintProps); } // Hopefully we now have some data to display, if not bail - DisplayFrame frame = frames.get(this.displayedDataTime); + DisplayFrame frame = null; + synchronized (frames) { + frame = frames.get(this.displayedDataTime); + } if (frame == null) { this.displayedDataTime = null; return; @@ -509,14 +527,24 @@ public class CcfpResource extends */ protected void addRecord(CcfpRecord obj) { DataTime dataTime = obj.getDataTime(); - Collection records = unprocessedRecords.get(dataTime); - if (records == null) { - records = new ArrayList(); - unprocessedRecords.put(dataTime, records); - this.dataTimes.add(dataTime); - Collections.sort(this.dataTimes); + if (resourceData.durationMatches(dataTime)) { + Collection records = null; + boolean brandNew = false; + synchronized (unprocessedRecords) { + records = unprocessedRecords.get(dataTime); + if (records == null) { + records = new HashSet(); + unprocessedRecords.put(dataTime, records); + brandNew = true; + } + } + if (brandNew) { + this.dataTimes.add(dataTime); + Collections.sort(this.dataTimes); + } + + records.add(obj); } - records.add(obj); } @Override @@ -677,16 +705,40 @@ public class CcfpResource extends @Override public void project(CoordinateReferenceSystem crs) throws VizException { - disposeFrames(); - // add as unprocessed to make sure frames created - for (DataTime time : frames.keySet()) { - DisplayFrame frame = frames.get(time); - if (frame != null) { - List copyList = new ArrayList( - frame.records); - unprocessedRecords.put(time, copyList); + synchronized (frames) { + disposeFrames(); + // add as unprocessed to make sure frames created + for (DataTime time : frames.keySet()) { + DisplayFrame frame = frames.get(time); + if (frame != null) { + List copyList = new ArrayList( + frame.records); + synchronized (unprocessedRecords) { + unprocessedRecords.put(time, copyList); + } + } } } } + @Override + public void remove(DataTime time) { + super.remove(time); + Collection notNeeded = null; + synchronized (unprocessedRecords) { + notNeeded = unprocessedRecords.remove(time); + } + if (notNeeded != null) { + notNeeded.clear(); + } + + DisplayFrame frame = null; + synchronized (frames) { + frame = frames.remove(time); + } + if (frame != null) { + frame.dispose(); + } + } + } diff --git a/cave/com.raytheon.uf.viz.ccfp/src/com/raytheon/uf/viz/ccfp/rsc/CcfpResourceData.java b/cave/com.raytheon.uf.viz.ccfp/src/com/raytheon/uf/viz/ccfp/rsc/CcfpResourceData.java index c38b14c474..ae946ce57f 100644 --- a/cave/com.raytheon.uf.viz.ccfp/src/com/raytheon/uf/viz/ccfp/rsc/CcfpResourceData.java +++ b/cave/com.raytheon.uf.viz.ccfp/src/com/raytheon/uf/viz/ccfp/rsc/CcfpResourceData.java @@ -31,12 +31,10 @@ import com.raytheon.uf.common.status.IUFStatusHandler; import com.raytheon.uf.common.status.UFStatus; import com.raytheon.uf.common.status.UFStatus.Priority; import com.raytheon.uf.common.time.DataTime; -import com.raytheon.uf.viz.ccfp.Activator; import com.raytheon.uf.viz.core.exception.VizException; import com.raytheon.uf.viz.core.rsc.AbstractRequestableResourceData; import com.raytheon.uf.viz.core.rsc.AbstractVizResource; import com.raytheon.uf.viz.core.rsc.LoadProperties; -import com.raytheon.uf.viz.core.status.StatusConstants; /** * @@ -56,8 +54,9 @@ import com.raytheon.uf.viz.core.status.StatusConstants; */ @XmlAccessorType(XmlAccessType.NONE) public class CcfpResourceData extends AbstractRequestableResourceData { - private static final transient IUFStatusHandler statusHandler = UFStatus.getHandler(CcfpResourceData.class); - + private static final transient IUFStatusHandler statusHandler = UFStatus + .getHandler(CcfpResourceData.class); + // This flag determnies if we draw lines and polygons @XmlAttribute private boolean displayArea = true; @@ -69,35 +68,34 @@ public class CcfpResourceData extends AbstractRequestableResourceData { // This flag determines if we display text boxes @XmlAttribute private boolean displayText = true; - + // Filter by coverage @XmlAttribute private int coverageFilter = 0; - - + @XmlAttribute private int validDuration = 0; - + @Override public boolean equals(Object obj) { if (!super.equals(obj)) { return false; } - + if (obj instanceof CcfpResourceData == false) { return false; } - + CcfpResourceData other = (CcfpResourceData) obj; if (other.coverageFilter != this.coverageFilter) { return false; } - + if (other.validDuration != this.validDuration) { return false; } - + if (other.displayText != this.displayText) { return false; } @@ -109,7 +107,7 @@ public class CcfpResourceData extends AbstractRequestableResourceData { if (other.displayMovement != this.displayMovement) { return false; } - + return true; } @@ -122,7 +120,7 @@ public class CcfpResourceData extends AbstractRequestableResourceData { DataTime[] originalTimes = super.getAvailableTimes(); ArrayList newTimes = new ArrayList(); for (DataTime time : originalTimes) { - if (time.getValidPeriod().getDuration() == validDuration * 1000) { + if (durationMatches(time)) { newTimes.add(time); } } @@ -147,6 +145,18 @@ public class CcfpResourceData extends AbstractRequestableResourceData { return nr; } + /** + * Checks if a DataTime's valid period matches the duration of the resource + * data + * + * @param time + * the time to check + * @return true if the durations are equal, otherwise false + */ + protected boolean durationMatches(DataTime time) { + return time.getValidPeriod().getDuration() == validDuration * 1000; + } + public boolean isDisplayArea() { return displayArea; } @@ -185,6 +195,6 @@ public class CcfpResourceData extends AbstractRequestableResourceData { public void setValidDuration(int validDuration) { this.validDuration = validDuration; - } + } } diff --git a/cave/com.raytheon.uf.viz.core/META-INF/services/com.raytheon.uf.common.serialization.ISerializableObject b/cave/com.raytheon.uf.viz.core/META-INF/services/com.raytheon.uf.common.serialization.ISerializableObject index db1c578231..88456c2959 100644 --- a/cave/com.raytheon.uf.viz.core/META-INF/services/com.raytheon.uf.common.serialization.ISerializableObject +++ b/cave/com.raytheon.uf.viz.core/META-INF/services/com.raytheon.uf.common.serialization.ISerializableObject @@ -17,7 +17,6 @@ com.raytheon.uf.viz.core.rsc.capabilities.LabelableCapability com.raytheon.uf.viz.core.rsc.capabilities.DensityCapability com.raytheon.uf.viz.core.rsc.capabilities.DisplayTypeCapability com.raytheon.uf.viz.core.rsc.capabilities.MagnificationCapability -com.raytheon.uf.viz.core.rsc.capabilities.MultiChannelCapability com.raytheon.uf.viz.core.rsc.capabilities.PointCapability com.raytheon.uf.viz.core.rsc.capabilities.TimeMatchBasisCapability com.raytheon.uf.viz.core.rsc.GenericResourceData diff --git a/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/drawables/ColorMapParameters.java b/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/drawables/ColorMapParameters.java index 3be9fb188e..1cd8a2f8d5 100644 --- a/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/drawables/ColorMapParameters.java +++ b/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/drawables/ColorMapParameters.java @@ -187,6 +187,8 @@ public class ColorMapParameters implements Cloneable, ISerializableObject { private boolean mirror; + private double noDataValue = Double.NaN; + @XmlElement private PersistedParameters persisted = new PersistedParameters(); @@ -1009,4 +1011,20 @@ public class ColorMapParameters implements Cloneable, ISerializableObject { this.colorMapMax = params.colorMapMax; } } + + /** + * @return the noDataValue + */ + public double getNoDataValue() { + return noDataValue; + } + + /** + * @param noDataValue + * the noDataValue to set + */ + public void setNoDataValue(double noDataValue) { + this.noDataValue = noDataValue; + } + } diff --git a/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/drawables/ImagingSupport.java b/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/drawables/ImagingSupport.java index 764209e217..40d2baa3f3 100644 --- a/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/drawables/ImagingSupport.java +++ b/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/drawables/ImagingSupport.java @@ -124,7 +124,7 @@ public class ImagingSupport { bulk.toArray(new DrawableImage[bulk.size()])); } - return rval & skipped; + return rval & !skipped; } /** diff --git a/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/drawables/ext/colormap/IMultiChannelImageExtension.java b/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/drawables/ext/colormap/IMultiChannelImageExtension.java deleted file mode 100644 index 92f6eeef2b..0000000000 --- a/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/drawables/ext/colormap/IMultiChannelImageExtension.java +++ /dev/null @@ -1,104 +0,0 @@ -/** - * This software was developed and / or modified by Raytheon Company, - * pursuant to Contract DG133W-05-CQ-1067 with the US Government. - * - * U.S. EXPORT CONTROLLED TECHNICAL DATA - * This software product contains export-restricted data whose - * export/transfer/disclosure is restricted by U.S. law. Dissemination - * to non-U.S. persons whether in the United States or abroad requires - * an export license or other authorization. - * - * Contractor Name: Raytheon Company - * Contractor Address: 6825 Pine Street, Suite 340 - * Mail Stop B8 - * Omaha, NE 68106 - * 402.291.0100 - * - * See the AWIPS II Master Rights File ("Master Rights File.pdf") for - * further licensing information. - **/ -package com.raytheon.uf.viz.core.drawables.ext.colormap; - -import java.util.Map; - -import com.raytheon.uf.viz.core.data.IColorMapDataRetrievalCallback; -import com.raytheon.uf.viz.core.drawables.ColorMapParameters; -import com.raytheon.uf.viz.core.drawables.IColormappedImage; -import com.raytheon.uf.viz.core.drawables.IImage; -import com.raytheon.uf.viz.core.drawables.ext.IImagingExtension; -import com.raytheon.uf.viz.core.exception.VizException; - -/** - * {@link IImagingExtension} used to create and draw multi-channel R,G,B images - * - *
- * 
- * SOFTWARE HISTORY
- * 
- * Date         Ticket#    Engineer    Description
- * ------------ ---------- ----------- --------------------------
- * Jul 5, 2012            mschenke     Initial creation
- * 
- * 
- * - * @author mschenke - * @version 1.0 - */ - -public interface IMultiChannelImageExtension extends IImagingExtension { - - public static enum Channel { - RED, GREEN, BLUE; - } - - public static class ChannelData { - public String name; - - public ColorMapParameters parameters; - - public boolean invert; - - public ChannelData(String name, ColorMapParameters params, - boolean invert) { - this.name = name; - this.parameters = params; - } - } - - public static interface IMultiChannelImage extends IImage { - - public Map getImageMapping(); - - public void setImageMapping(Map mapping); - - } - - public static interface IImageChannel extends IColormappedImage { - - public void setInverted(boolean inverted); - - } - - /** - * Construct a true color image, this - * - * @param channelMap - * @return - * @throws VizException - */ - public IMultiChannelImage constructImage( - Map imageMapping) throws VizException; - - /** - * Construct a IImageChannel for a IMultiChannelImage - * - * @param callback - * @param inverted - * @return - * @throws VizException - */ - public IImageChannel constructImage( - IColorMapDataRetrievalCallback callback, ChannelData channelData) - throws VizException; - -} diff --git a/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/rsc/ResourceList.java b/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/rsc/ResourceList.java index e841f720ad..b23d686208 100644 --- a/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/rsc/ResourceList.java +++ b/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/rsc/ResourceList.java @@ -158,6 +158,18 @@ public class ResourceList extends CopyOnWriteArrayList implements return add(rp); } + /** + * Function for determining if the {@link ResourcePair} can be added to the + * list. Default checks if the pair is already in the list and returns false + * if so + * + * @param e + * @return + */ + protected boolean canAdd(ResourcePair e) { + return contains(e) == false; + } + /* * (non-Javadoc) * @@ -165,7 +177,7 @@ public class ResourceList extends CopyOnWriteArrayList implements */ @Override public boolean add(ResourcePair e) { - if (e == null || contains(e)) { + if (e == null || canAdd(e) == false) { return false; } @@ -263,7 +275,7 @@ public class ResourceList extends CopyOnWriteArrayList implements } synchronized (this) { - if (contains(e)) { + if (canAdd(e) == false) { return false; } @@ -276,9 +288,7 @@ public class ResourceList extends CopyOnWriteArrayList implements super.add(i >= 0 ? (i + 1) : 0, e); synchronized (resourcesToInstantiate) { - if (resourcesToInstantiate.contains(e) == false) { - resourcesToInstantiate.add(e); - } + resourcesToInstantiate.add(e); } } @@ -300,7 +310,7 @@ public class ResourceList extends CopyOnWriteArrayList implements if (rp.getResource() == null) { Validate.notNull(rp.getResourceData()); } - if (contains(rp) == false) { + if (canAdd(rp)) { toAdd.add(rp); } } @@ -400,7 +410,13 @@ public class ResourceList extends CopyOnWriteArrayList implements super.remove(index); synchronized (resourcesToInstantiate) { - resourcesToInstantiate.remove(rp); + // Ensure rp is removed from list entirely + Iterator iter = resourcesToInstantiate.iterator(); + while (iter.hasNext()) { + if (iter.next() == rp) { + iter.remove(); + } + } } try { diff --git a/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/rsc/capabilities/MultiChannelCapability.java b/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/rsc/capabilities/MultiChannelCapability.java deleted file mode 100644 index 259b6d6799..0000000000 --- a/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/rsc/capabilities/MultiChannelCapability.java +++ /dev/null @@ -1,313 +0,0 @@ -/** - * This software was developed and / or modified by Raytheon Company, - * pursuant to Contract DG133W-05-CQ-1067 with the US Government. - * - * U.S. EXPORT CONTROLLED TECHNICAL DATA - * This software product contains export-restricted data whose - * export/transfer/disclosure is restricted by U.S. law. Dissemination - * to non-U.S. persons whether in the United States or abroad requires - * an export license or other authorization. - * - * Contractor Name: Raytheon Company - * Contractor Address: 6825 Pine Street, Suite 340 - * Mail Stop B8 - * Omaha, NE 68106 - * 402.291.0100 - * - * See the AWIPS II Master Rights File ("Master Rights File.pdf") for - * further licensing information. - **/ -package com.raytheon.uf.viz.core.rsc.capabilities; - -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.adapters.XmlAdapter; -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; - -import com.raytheon.uf.viz.core.drawables.ColorMapParameters; -import com.raytheon.uf.viz.core.drawables.ext.colormap.IMultiChannelImageExtension.Channel; -import com.raytheon.uf.viz.core.drawables.ext.colormap.IMultiChannelImageExtension.ChannelData; - -/** - * Capability for multi channel imagery - * - *
- * 
- * SOFTWARE HISTORY
- * 
- * Date         Ticket#    Engineer    Description
- * ------------ ---------- ----------- --------------------------
- * Dec 20, 2011            mschenke     Initial creation
- * 
- * 
- * - * @author mschenke - * @version 1.0 - */ -@XmlAccessorType(XmlAccessType.NONE) -public class MultiChannelCapability extends AbstractCapability { - - @XmlAccessorType(XmlAccessType.NONE) - public static class ChannelSerializable { - - @XmlAttribute - private String name; - - @XmlAttribute - private Channel channel; - - @XmlAttribute - private boolean invert; - - @XmlElement - private float dataMin; - - @XmlElement - private float dataMax; - - @XmlElement - private float cmapMin; - - @XmlElement - private float cmapMax; - - public ChannelSerializable() { - - } - - public ChannelSerializable(Channel channel, ChannelData channelData) { - this.channel = channel; - this.name = channelData.name; - this.invert = channelData.invert; - ColorMapParameters params = channelData.parameters; - this.dataMin = params.getDataMin(); - this.dataMax = params.getDataMax(); - this.cmapMin = params.getColorMapMin(); - this.cmapMax = params.getColorMapMax(); - } - - /** - * @return the name - */ - public String getName() { - return name; - } - - /** - * @param name - * the name to set - */ - public void setName(String name) { - this.name = name; - } - - /** - * @return the channel - */ - public Channel getChannel() { - return channel; - } - - /** - * @param channel - * the channel to set - */ - public void setChannel(Channel channel) { - this.channel = channel; - } - - /** - * @return the dataMin - */ - public float getDataMin() { - return dataMin; - } - - /** - * @param dataMin - * the dataMin to set - */ - public void setDataMin(float dataMin) { - this.dataMin = dataMin; - } - - /** - * @return the dataMax - */ - public float getDataMax() { - return dataMax; - } - - /** - * @param dataMax - * the dataMax to set - */ - public void setDataMax(float dataMax) { - this.dataMax = dataMax; - } - - /** - * @return the cmapMin - */ - public float getCmapMin() { - return cmapMin; - } - - /** - * @param cmapMin - * the cmapMin to set - */ - public void setCmapMin(float cmapMin) { - this.cmapMin = cmapMin; - } - - /** - * @return the cmapMax - */ - public float getCmapMax() { - return cmapMax; - } - - /** - * @param cmapMax - * the cmapMax to set - */ - public void setCmapMax(float cmapMax) { - this.cmapMax = cmapMax; - } - - /** - * @return the invert - */ - public boolean isInvert() { - return invert; - } - - /** - * @param invert - * the invert to set - */ - public void setInvert(boolean invert) { - this.invert = invert; - } - - } - - public static class Marshaller extends - XmlAdapter> { - - /* - * (non-Javadoc) - * - * @see - * javax.xml.bind.annotation.adapters.XmlAdapter#unmarshal(java.lang - * .Object) - */ - @Override - public HashMap unmarshal(ChannelSerializable[] v) - throws Exception { - HashMap channelMap = new HashMap(); - for (ChannelSerializable cs : v) { - ColorMapParameters params = new ColorMapParameters(); - params.setDataMin(cs.dataMin); - params.setDataMax(cs.dataMax); - params.setColorMapMin(cs.cmapMin); - params.setColorMapMax(cs.cmapMax); - channelMap.put(cs.channel, new ChannelData(cs.name, params, - cs.invert)); - } - return channelMap; - } - - /* - * (non-Javadoc) - * - * @see - * javax.xml.bind.annotation.adapters.XmlAdapter#marshal(java.lang.Object - * ) - */ - @Override - public ChannelSerializable[] marshal(HashMap v) - throws Exception { - ChannelSerializable[] serializable = new ChannelSerializable[v - .size()]; - int i = 0; - for (Entry entry : v.entrySet()) { - ChannelSerializable cs = new ChannelSerializable( - entry.getKey(), entry.getValue()); - serializable[i++] = cs; - } - return serializable; - } - - } - - @XmlJavaTypeAdapter(value = Marshaller.class) - private HashMap channelMap = new HashMap(); - - private String[] names; - - /** - * @return the names - */ - public String[] getNames() { - return names; - } - - /** - * @param names - * the names to set - */ - public void setNames(String[] names) { - this.names = names; - } - - /** - * @return the channelMap - */ - public Map getChannelMap() { - return channelMap; - } - - /** - * @param channelMap - * the channelMap to set - */ - public void setChannelMap(HashMap channelMap) { - if (channelMap == null) { - channelMap = new HashMap(); - } - this.channelMap = channelMap; - capabilityChanged(); - } - - /* - * (non-Javadoc) - * - * @see com.raytheon.uf.viz.core.rsc.capabilities.AbstractCapability# - * capabilityChanged() - */ - @Override - public void capabilityChanged() { - super.capabilityChanged(); - } - - /* - * (non-Javadoc) - * - * @see com.raytheon.uf.viz.core.rsc.capabilities.AbstractCapability#clone() - */ - @Override - public AbstractCapability clone() { - MultiChannelCapability cap = new MultiChannelCapability(); - cap.channelMap = new HashMap(channelMap); - return cap; - } - -} diff --git a/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/tile/Tile.java b/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/tile/Tile.java new file mode 100644 index 0000000000..bdf90b372f --- /dev/null +++ b/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/tile/Tile.java @@ -0,0 +1,195 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.uf.viz.core.tile; + +import java.awt.Rectangle; +import java.util.ArrayList; +import java.util.List; + +import org.geotools.coverage.grid.GridGeometry2D; +import org.opengis.coverage.grid.GridEnvelope; +import org.opengis.geometry.Envelope; + +import com.vividsolutions.jts.geom.Coordinate; +import com.vividsolutions.jts.geom.Geometry; +import com.vividsolutions.jts.geom.prep.PreparedGeometry; +import com.vividsolutions.jts.geom.prep.PreparedGeometryFactory; + +/** + * Object that represents a single Tile. Contains a level and GridGeometry as + * well as a border which is used in {@link #intersects(Geometry)} + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date         Ticket#    Engineer    Description
+ * ------------ ---------- ----------- --------------------------
+ * Aug 8, 2012            mschenke     Initial creation
+ * 
+ * 
+ * + * @author mschenke + * @version 1.0 + */ + +public class Tile { + + public final int tileLevel; + + public final GridGeometry2D tileGeometry; + + public final List tileBorder; + + /** + * Create a Tile specifying a border for intersection checks + * + * @param tileLevel + * @param tileGeometry + * @param tileBorder + */ + public Tile(int tileLevel, GridGeometry2D tileGeometry, Geometry tileBorder) { + this.tileLevel = tileLevel; + this.tileGeometry = tileGeometry; + if (tileBorder != null) { + int num = tileBorder.getNumGeometries(); + this.tileBorder = new ArrayList(num); + for (int n = 0; n < num; ++n) { + this.tileBorder.add(PreparedGeometryFactory.prepare(tileBorder + .getGeometryN(n))); + } + } else { + this.tileBorder = null; + } + } + + /** + * Creates a Tile with no border. intersects calls will always return true + * + * @param tileLevel + * @param tileGeometry + */ + public Tile(int tileLevel, GridGeometry2D tileGeometry) { + this(tileLevel, tileGeometry, null); + } + + public Rectangle getRectangle() { + GridEnvelope env = tileGeometry.getGridRange(); + return new Rectangle(env.getLow(0), env.getLow(1), env.getSpan(0), + env.getSpan(1)); + } + + /** + * Checks if the geometry intersects the Tile. Tiles without a + * {@link #tileBorder} will always return true + * + * @param geometry + * @return + */ + public boolean intersects(Geometry geometry) { + if (tileBorder == null) { + return true; + } + for (PreparedGeometry border : tileBorder) { + if (border.intersects(geometry)) { + return true; + } + } + return false; + } + + /** + * Checks to see if the x/y coordinate is contained by the Tile's CRS + * Envelope + * + * @param x + * @param y + * @return + */ + public boolean crsContains(double x, double y) { + Envelope env = tileGeometry.getEnvelope(); + return env.getMinimum(0) <= x && env.getMaximum(0) >= x + && env.getMinimum(1) <= y && env.getMaximum(1) >= y; + } + + /** + * Checks to see if the Coordinate is contained by the Tile's CRS Envelope + * + * @param c + * @return + */ + public boolean crsContains(Coordinate c) { + return crsContains(c.x, c.y); + } + + /** + * Checks to see if the x/y coordinate is contained by the Tile's grid + * envelope + * + * @param gridX + * @param gridY + * @return + */ + public boolean gridContains(int gridX, int gridY) { + GridEnvelope ge = tileGeometry.getGridRange(); + return ge.getLow(0) <= gridX && ge.getHigh(0) >= gridX + && ge.getLow(1) <= gridY && ge.getHigh(1) >= gridY; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((tileGeometry == null) ? 0 : tileGeometry.hashCode()); + result = prime * result + tileLevel; + return result; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Tile other = (Tile) obj; + if (tileGeometry == null) { + if (other.tileGeometry != null) + return false; + } else if (!tileGeometry.equals(other.tileGeometry)) + return false; + if (tileLevel != other.tileLevel) + return false; + return true; + } + +} diff --git a/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/tile/TileLevel.java b/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/tile/TileLevel.java new file mode 100644 index 0000000000..a6cf76a71c --- /dev/null +++ b/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/tile/TileLevel.java @@ -0,0 +1,418 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.uf.viz.core.tile; + +import java.util.ArrayList; +import java.util.List; + +import org.geotools.coverage.grid.GeneralGridEnvelope; +import org.geotools.coverage.grid.GeneralGridGeometry; +import org.geotools.coverage.grid.GridGeometry2D; +import org.geotools.geometry.Envelope2D; +import org.geotools.geometry.jts.JTS; +import org.geotools.referencing.crs.DefaultGeographicCRS; +import org.geotools.referencing.operation.DefaultMathTransformFactory; +import org.opengis.coverage.grid.GridEnvelope; +import org.opengis.geometry.Envelope; +import org.opengis.referencing.datum.PixelInCell; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.referencing.operation.TransformException; + +import com.raytheon.uf.common.geospatial.CRSCache; +import com.raytheon.uf.common.geospatial.util.WorldWrapCorrector; +import com.vividsolutions.jts.geom.Coordinate; +import com.vividsolutions.jts.geom.Geometry; + +/** + * This object represents a single tile level. It does this by containing a 2 + * dimensional array of {@link Tile} objects. + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date         Ticket#    Engineer    Description
+ * ------------ ---------- ----------- --------------------------
+ * Aug 8, 2012            mschenke     Initial creation
+ * 
+ * 
+ * + * @author mschenke + * @version 1.0 + */ + +public class TileLevel { + + /** Tile level's GridGeometry */ + private GridGeometry2D levelGeometry; + + /** GridGeometry tile level was created for */ + private GeneralGridGeometry targetGeometry; + + // Cached MathTransforms + private MathTransform crsToGrid; + + private MathTransform gridToCRS; + + private MathTransform tileCRSToTargetGrid; + + /** Target grid to lat/lon for world wrap correcting */ + private MathTransform targetGridToLatLon; + + /** World wrap corrector, corrects Tile borders */ + private WorldWrapCorrector corrector; + + /** Level of this TileLevel */ + private int tileLevel; + + /** size for tiles in this level */ + private int tileSize; + + /** Pixel density of the tile level */ + private double pixelDensity; + + /** Tile array */ + private Tile[][] tiles; + + TileLevel(GridGeometry2D levelGeometry, GeneralGridGeometry targetGeometry, + int tileLevel, int tileSize) { + this.levelGeometry = levelGeometry; + this.targetGeometry = targetGeometry; + this.tileLevel = tileLevel; + this.tileSize = tileSize; + intialize(levelGeometry.getGridRange(), levelGeometry.getEnvelope()); + } + + private void intialize(GridEnvelope range, Envelope envelope) { + int width = range.getSpan(0); + int height = range.getSpan(1); + int fullTileDimX = width / tileSize; + int tileRemainderX = width % tileSize; + int fullTileDimY = height / tileSize; + int tileRemainderY = height % tileSize; + + int totalTilesX = fullTileDimX; + if (tileRemainderX > 0) { + totalTilesX++; + } + + int totalTilesY = fullTileDimY; + if (tileRemainderY > 0) { + totalTilesY++; + } + + tiles = new Tile[totalTilesY][totalTilesX]; + + try { + gridToCRS = levelGeometry.getGridToCRS(PixelInCell.CELL_CORNER); + crsToGrid = gridToCRS.inverse(); + + DefaultMathTransformFactory factory = new DefaultMathTransformFactory(); + tileCRSToTargetGrid = factory.createConcatenatedTransform( + CRSCache.getInstance().findMathTransform( + levelGeometry.getCoordinateReferenceSystem(), + targetGeometry.getCoordinateReferenceSystem()), + targetGeometry.getGridToCRS(PixelInCell.CELL_CORNER) + .inverse()); + targetGridToLatLon = factory.createConcatenatedTransform( + targetGeometry.getGridToCRS(PixelInCell.CELL_CORNER), + CRSCache.getInstance().findMathTransform( + targetGeometry.getCoordinateReferenceSystem(), + DefaultGeographicCRS.WGS84)); + corrector = new WorldWrapCorrector(targetGeometry); + + // Calculate pixel density + // Grab the center x, 3/4 y of the map + double mapXCenter = targetGeometry.getGridRange().getSpan(0) * 0.5; + double mapYCenter = targetGeometry.getGridRange().getSpan(1) * 0.75; + + double[] input = new double[] { mapXCenter, mapYCenter, + mapXCenter + 1, mapYCenter + 1 }; + double[] output = new double[input.length]; + + tileCRSToTargetGrid.inverse().transform(input, 0, output, 0, 2); + levelGeometry.getGridToCRS(PixelInCell.CELL_CORNER).inverse() + .transform(output, 0, input, 0, 2); + pixelDensity = 1.0 / Math.abs(new Coordinate(input[0], input[1], + 0.0).distance(new Coordinate(input[2], input[3], 0.0))); + } catch (Exception e) { + throw new RuntimeException( + "Cannot tranform tile CRS into target CRS", e); + } + + } + + /** + * Returns the pixel density for the tile level. This is the approximate + * number of target grid pixels a single tile grid pixel takes up + * + * @return + */ + public double getPixelDensity() { + return pixelDensity; + } + + /** + * The number of tiles in the x direction (number of columns) + * + * @return + */ + public int getNumXTiles() { + return tiles[0].length; + } + + /** + * The number of tiles in the y direction (number of rows) + * + * @return + */ + public int getNumYTiles() { + return tiles.length; + } + + /** + * Level of this TileLevel + * + * @return + */ + public int getLevel() { + return tileLevel; + } + + /** + * Get the Tile at the specified x/y index in the tile set + * + * @param x + * @param y + * @return + */ + public Tile getTile(int x, int y) { + Tile tile = tiles[y][x]; + if (tile == null) { + synchronized (tiles) { + // Double check tile to see if another thread created it + tile = tiles[y][x]; + if (tile == null) { + tiles[y][x] = tile = createTile(x, y); + } + } + } + return tile; + } + + /** + * Gets the Tile for the specified tile grid location + * + * @param x + * @param y + * @return + */ + public Tile getTile(double x, double y) { + double xIdx = x / tileSize; + double yIdx = y / tileSize; + if (xIdx >= 0 && yIdx >= 0 && xIdx < getNumXTiles() + && yIdx < getNumYTiles()) { + Tile tile = getTile((int) xIdx, (int) yIdx); + if (tile.gridContains((int) x, (int) y)) { + return tile; + } + } + return null; + } + + /** + * Transforms TileLevel crs x,y into tile level grid space x,y + * + * @param x + * @param y + * @return + */ + public double[] crsToGrid(double x, double y) throws TransformException { + double[] out = new double[2]; + crsToGrid.transform(new double[] { x, y }, 0, out, 0, 1); + return out; + } + + /** + * Creates a Tile for the specified x/y {@link #tiles} index + * + * @param x + * @param y + * @return + */ + private Tile createTile(int x, int y) { + GridEnvelope range = levelGeometry.getGridRange(); + // Get grid range ranges and calculate grid range for the tile + int startX = range.getLow(0); + int startY = range.getLow(1); + int endX = range.getHigh(0) + 1; + int endY = range.getHigh(1) + 1; + + int tileY = startY + y * tileSize; + int tileX = startX + x * tileSize; + + int tileEndX = Math.min(endX, tileX + tileSize); + int tileEndY = Math.min(endY, tileY + tileSize); + + // Convert grid range into crs envelope range + double[] in = new double[] { tileX, tileY, tileEndX, tileEndY }; + double[] out = new double[in.length]; + try { + gridToCRS.transform(in, 0, out, 0, 2); + } catch (TransformException e) { + throw new RuntimeException("Error getting tile envelope from grid", + e); + } + double envTileX = out[0]; + double envTileY = out[1]; + double envTileEndX = out[2]; + double envTileEndY = out[3]; + + // Create tile GridGeometry + range = new GeneralGridEnvelope(new int[] { tileX, tileY }, new int[] { + tileEndX, tileEndY }, false); + GridGeometry2D tileGridGeom = new GridGeometry2D(range, new Envelope2D( + levelGeometry.getCoordinateReferenceSystem(), out[0], envTileY, + envTileEndX - envTileX, envTileEndY - envTileY)); + + // Calculate the border in target grid space for the Tile + Geometry border = null; + try { + double[] UL = new double[] { envTileX, envTileY }; + double[] UR = new double[] { envTileEndX, envTileY }; + double[] LR = new double[] { envTileEndX, envTileEndY }; + double[] LL = new double[] { envTileX, envTileEndY }; + + // Create tile border based on pixel density tile level 0 should + // always have threshold of 1.0 + border = createTileBorder(UL, UR, LR, LL, + Math.max(range.getSpan(0) / 4, 1), + Math.max(range.getSpan(1) / 4, 1), + tileLevel > 0 ? Math.max(pixelDensity, 1.0) : 1.0); + } catch (TransformException e) { + // Invalid geometry, don't add a border + } + + // Create the Tile object + return new Tile(tileLevel, tileGridGeom, border); + } + + /** + * Ensures all Tile objects are created for the level. This method can takes + * lots of time depending on grid resolution of level and {@link #tileSize}. + * Tiles are created dynamically as requested otherwise + */ + public void populateTiles() { + int totalTilesY = getNumYTiles(); + int totalTilesX = getNumXTiles(); + for (int y = 0; y < totalTilesY; ++y) { + for (int x = 0; x < totalTilesX; ++x) { + if (tiles[y][x] == null) { + tiles[y][x] = createTile(x, y); + } + } + } + } + + private Geometry createTileBorder(double[] UL, double[] UR, double[] LR, + double[] LL, int maxHorDivisions, int maxVertDivisions, + double threshold) throws TransformException { + List borderPoints = new ArrayList( + maxVertDivisions * 2 + maxHorDivisions * 2); + double[] out = new double[2]; + + // UL to UR + tileCRSToTargetGrid.transform(UL, 0, out, 0, 1); + borderPoints.add(new Coordinate(out[0], out[1])); + calculateBorder(borderPoints, UL, null, UR, null, maxHorDivisions, + threshold); + + // UR to LR + tileCRSToTargetGrid.transform(UR, 0, out, 0, 1); + borderPoints.add(new Coordinate(out[0], out[1])); + calculateBorder(borderPoints, UR, null, LR, null, maxVertDivisions, 1.0); + + // LR to LL + tileCRSToTargetGrid.transform(LR, 0, out, 0, 1); + borderPoints.add(new Coordinate(out[0], out[1])); + calculateBorder(borderPoints, LR, null, LL, null, maxHorDivisions, 1.0); + + // LL to UL + tileCRSToTargetGrid.transform(LL, 0, out, 0, 1); + borderPoints.add(new Coordinate(out[0], out[1])); + calculateBorder(borderPoints, LL, null, UL, null, maxVertDivisions, 1.0); + + // Add start point to complete linear ring + tileCRSToTargetGrid.transform(UL, 0, out, 0, 1); + borderPoints.add(new Coordinate(out[0], out[1])); + + // Create Geometry and world wrap correct (need to be in lat/lon) + return JTS.transform(corrector.correct(JTS.transform(TileSet.gf + .createPolygon(TileSet.gf.createLinearRing(borderPoints + .toArray(new Coordinate[borderPoints.size()])), null), + targetGridToLatLon)), targetGridToLatLon.inverse()); + } + + private int calculateBorder(List borderList, double[] point1, + double[] transformedPoint1, double[] point3, + double[] transformedPoint3, double maxNumDivs, double threshold) + throws TransformException { + if (transformedPoint1 == null) { + transformedPoint1 = new double[point1.length]; + tileCRSToTargetGrid.transform(point1, 0, transformedPoint1, 0, 1); + } + if (transformedPoint3 == null) { + transformedPoint3 = new double[point3.length]; + tileCRSToTargetGrid.transform(point3, 0, transformedPoint3, 0, 1); + } + if (transformedPoint1 == null || transformedPoint3 == null) { + // if the image has some points outside the valid range of the + // screen then give up optimizing and assume the max number of + // points. + return (int) Math.ceil(maxNumDivs); + } + double[] point2 = { (point1[0] + point3[0]) / 2, + (point1[1] + point3[1]) / 2 }; + double[] transformedPoint2 = new double[point2.length]; + tileCRSToTargetGrid.transform(point2, 0, transformedPoint2, 0, 1); + double[] interp2 = { (transformedPoint1[0] + transformedPoint3[0]) / 2, + (transformedPoint1[1] + transformedPoint3[1]) / 2 }; + double dX = transformedPoint2[0] - interp2[0]; + double dY = transformedPoint2[1] - interp2[1]; + double d = Math.hypot(dX, dY); + if (d < threshold || maxNumDivs < 1) { + return 1; + } else { + int nd1 = calculateBorder(borderList, point1, transformedPoint1, + point2, transformedPoint2, maxNumDivs / 2, threshold); + borderList.add(new Coordinate(transformedPoint2[0], + transformedPoint2[1])); + if (nd1 * 2 >= maxNumDivs) { + nd1 = (int) Math.ceil(maxNumDivs); + } + int nd2 = calculateBorder(borderList, point2, transformedPoint2, + point3, transformedPoint3, maxNumDivs / 2, threshold); + if (nd2 * 2 >= maxNumDivs) { + nd2 = (int) Math.ceil(maxNumDivs); + } + return (Math.max(nd1, nd2) * 2); + } + } +} diff --git a/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/tile/TileSetRenderable.java b/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/tile/TileSetRenderable.java new file mode 100644 index 0000000000..5dc7b02223 --- /dev/null +++ b/cave/com.raytheon.uf.viz.core/src/com/raytheon/uf/viz/core/tile/TileSetRenderable.java @@ -0,0 +1,462 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.uf.viz.core.tile; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.geotools.coverage.grid.GeneralGridGeometry; +import org.geotools.coverage.grid.GridGeometry2D; +import org.geotools.referencing.crs.DefaultGeographicCRS; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.referencing.operation.TransformException; + +import com.raytheon.uf.common.geospatial.CRSCache; +import com.raytheon.uf.common.status.IUFStatusHandler; +import com.raytheon.uf.common.status.UFStatus; +import com.raytheon.uf.common.status.UFStatus.Priority; +import com.raytheon.uf.viz.core.DrawableImage; +import com.raytheon.uf.viz.core.IExtent; +import com.raytheon.uf.viz.core.IGraphicsTarget; +import com.raytheon.uf.viz.core.IMesh; +import com.raytheon.uf.viz.core.drawables.IColormappedImage; +import com.raytheon.uf.viz.core.drawables.IImage; +import com.raytheon.uf.viz.core.drawables.IImage.Status; +import com.raytheon.uf.viz.core.drawables.IRenderable; +import com.raytheon.uf.viz.core.drawables.PaintProperties; +import com.raytheon.uf.viz.core.exception.VizException; +import com.raytheon.uf.viz.core.jobs.JobPool; +import com.raytheon.uf.viz.core.rsc.capabilities.ImagingCapability; +import com.vividsolutions.jts.geom.Coordinate; + +/** + * Renderable tile set class that creates a {@link TileSet} and renders images + * for tiles displayed using the {@link TileImageCreator} passed in at + * construction + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date         Ticket#    Engineer    Description
+ * ------------ ---------- ----------- --------------------------
+ * Aug 8, 2012            mschenke     Initial creation
+ * 
+ * 
+ * + * @author mschenke + * @version 1.0 + */ + +public class TileSetRenderable implements IRenderable { + + public static interface TileImageCreator { + + /** + * Create a complete DrawableImage for the given tile on the + * targetGeometry + */ + public DrawableImage createTileImage(IGraphicsTarget target, Tile tile, + GeneralGridGeometry targetGeometry) throws VizException; + + } + + private class TileImageCreatorTask implements Runnable { + + private IGraphicsTarget target; + + private Tile tile; + + private TileImageCreatorTask(IGraphicsTarget target, Tile tile) { + this.target = target; + this.tile = tile; + } + + @Override + public void run() { + try { + DrawableImage di = tileCreator.createTileImage(target, tile, + tileSet.getTargetGeometry()); + if (di != null) { + di.getImage().stage(); + } + addTileImage(tile, di); + } catch (VizException e) { + statusHandler.handle(Priority.PROBLEM, e.getLocalizedMessage(), + e); + } + } + } + + protected static final transient IUFStatusHandler statusHandler = UFStatus + .getHandler(TileSetRenderable.class); + + /** Screen pixel to image pixel threshold at which we change levels */ + protected static final double LEVEL_CHANGE_THRESHOLD = 2.0; + + /** Job pool for tile creation */ + protected static final JobPool tileCreationPool = new JobPool( + "Creating Image Tiles", 10, false); + + /** Job map, should only have one job running per tile at a time */ + protected Map jobMap = new ConcurrentHashMap(); + + /** Image map for tiles */ + protected Map imageMap = new HashMap(); + + /** Full resolution tile set GridGeometry2D */ + protected final GridGeometry2D tileSetGeometry; + + protected final TileImageCreator tileCreator; + + /** Desired size for each tile */ + protected final int tileSize; + + /** Number of tile levels to create */ + protected final int tileLevels; + + /** {@link TileSet} object, manages tiles */ + protected TileSet tileSet; + + /** Transform for tileset CRS to lat/lon */ + protected final MathTransform localProjToLL; + + /** Transform for lat/lon to tileset CRS */ + protected final MathTransform llToLocalProj; + + /** Stored imaging capability */ + protected final ImagingCapability imaging; + + /** Last painted tile level, used for interrogating proper level */ + protected int lastPaintedLevel; + + /** The ratio of target grid pixels / image pixel for each tile level */ + protected double[] pixelWidth; + + /** + * Constructs a tile set renderable, creators needs to call + * {@link #project(GeneralGridGeometry)} before the renderable can be used + * + * @param resource + * @param tileSetGeometry + * @param tileCreator + * @param tileLevels + * @param tileSize + */ + public TileSetRenderable(ImagingCapability imaging, + GridGeometry2D tileSetGeometry, TileImageCreator tileCreator, + int tileLevels, int tileSize) { + this.tileSetGeometry = tileSetGeometry; + this.tileCreator = tileCreator; + this.tileLevels = tileLevels; + this.tileSize = tileSize; + this.pixelWidth = new double[tileLevels]; + this.imaging = imaging; + + try { + // Set lat/lon math transforms for tile set + llToLocalProj = CRSCache.getInstance().findMathTransform( + DefaultGeographicCRS.WGS84, + tileSetGeometry.getCoordinateReferenceSystem()); + localProjToLL = llToLocalProj.inverse(); + } catch (Exception e) { + throw new IllegalArgumentException( + "Could not get tranform from tile crs to lat/lon", e); + } + } + + /** + * Projects the tile set for use with the target geometry + * + * @param targetGeometry + */ + public synchronized void project(GeneralGridGeometry targetGeometry) { + // dispose the old TileSet + if (tileSet != null) { + tileSet.dispose(); + } + // Create TileSet for new target geometry + tileSet = TileSet.getTileSet(tileSetGeometry, targetGeometry, + tileLevels, tileSize); + + // Clear out meshes and create new ones cloning old ones + for (DrawableImage di : imageMap.values()) { + if (di != null) { + IMesh currentMesh = di.getCoverage().getMesh(); + if (currentMesh != null) { + try { + di.getCoverage().setMesh( + currentMesh.clone(targetGeometry)); + } catch (VizException e) { + statusHandler.handle(Priority.PROBLEM, + e.getLocalizedMessage(), e); + } + currentMesh.dispose(); + } + } + } + + // Get the pixel densities for each tile level. This is approximately + // how many target grid pixels per image pixel there are for the level + for (int level = 0; level < tileLevels; ++level) { + TileLevel tileLevel = tileSet.getTileLevel(level); + pixelWidth[level] = tileLevel.getPixelDensity(); + } + } + + /* + * (non-Javadoc) + * + * @see + * com.raytheon.uf.viz.core.drawables.IRenderable#paint(com.raytheon.uf. + * viz.core.IGraphicsTarget, + * com.raytheon.uf.viz.core.drawables.PaintProperties) + */ + @Override + public void paint(IGraphicsTarget target, PaintProperties paintProps) + throws VizException { + Collection images = getImagesToRender(target, paintProps); + target.drawRasters(paintProps, + images.toArray(new DrawableImage[images.size()])); + } + + /** + * Disposes the tile set and any data associated with it + */ + public synchronized void dispose() { + // Make sure any lingering jobs are canceled and joined on + for (Runnable job : jobMap.values()) { + tileCreationPool.cancel(job); + } + tileCreationPool.join(); + + // Dispose the tile set + if (tileSet != null) { + tileSet.dispose(); + tileSet = null; + } + + // Dispose of all the images for the tile set + for (DrawableImage image : imageMap.values()) { + if (image != null) { + image.dispose(); + } + } + imageMap.clear(); + } + + /** + * Get the {@link DrawableImage} list to display for the given target and + * paint properties + * + * @param target + * @param paintProps + * @return + * @throws VizException + */ + public synchronized Collection getImagesToRender( + IGraphicsTarget target, PaintProperties paintProps) + throws VizException { + double screenToWorldRatio = paintProps.getCanvasBounds().width + / paintProps.getView().getExtent().getWidth(); + + int usedTileLevel = tileLevels - 1; + + /* + * pixelRatios[usedTileLevel] * screenToWorldRatio gives us + * canvasPixels/image pixel at the level, We should use the level if + * there are less than LEVEL_CHANGE_THRESHOLD canvas pixels per image + * pixel + */ + while ((pixelWidth[usedTileLevel] * screenToWorldRatio > LEVEL_CHANGE_THRESHOLD) + && usedTileLevel > 0) { + usedTileLevel--; + } + lastPaintedLevel = usedTileLevel; + + return getImagesWithinExtent(target, paintProps.getView().getExtent(), + usedTileLevel, 0); + } + + /** + * + * @param target + * @param paintProps + * @param level + * @param depth + * @return + * @throws VizException + */ + protected List getImagesWithinExtent(IGraphicsTarget target, + IExtent extent, int level, int depth) throws VizException { + if (tileSet == null) { + // Early exit condition, disposed or haven't projected yet + return Collections.emptyList(); + } + + // Flag to determine if we should draw lower tile levels. This will be + // the case if we don't have all the images for all intersecting tiles + // at this level + boolean needDrawLower = false; + // Get the intersecting tiles for the level + Collection intersecting = tileSet.getIntersectingTiles(level, + extent); + + // These Tiles still need images created for them + List tilesNeedingImage = new ArrayList(intersecting.size()); + List drawableImages = new ArrayList( + intersecting.size()); + + for (Tile tile : intersecting) { + // Flag to indicate if a tile needs an image created for it + boolean needsImage = false; + DrawableImage di = imageMap.get(tile); + if (di != null) { + IImage image = di.getImage(); + if (image.getStatus() == Status.FAILED + || image.getStatus() == Status.INVALID) { + // Image is invalid, re-request creation + needsImage = true; + } else { + image.setBrightness(imaging.getBrightness()); + image.setContrast(imaging.getContrast()); + image.setInterpolated(imaging.isInterpolationState()); + + if (image.getStatus() != Status.LOADED) { + needDrawLower = true; + } + drawableImages.add(di); + } + } else { + needsImage = true; + } + + if (needsImage) { + tilesNeedingImage.add(tile); + needDrawLower = true; + } + } + + if (depth == 0) { + // Only request images to be created if we are at the desired level + // i.e. the recursion depth is 0 + if (tilesNeedingImage.isEmpty()) { + // All intersecting tiles are loaded for this level, cancel any + // jobs running for tiles we don't need anymore (may be case if + // zooming or panning) + for (Runnable job : jobMap.values()) { + tileCreationPool.cancel(job); + } + jobMap.clear(); + } else { + target.setNeedsRefresh(true); + // Create tiles needing images + createTileImages(target, tilesNeedingImage); + } + } + + // Draw lower resolution data first + if (needDrawLower && (level + 1) < tileLevels) { + // put lower levels first in the list so they are drawn first. + List lowerImages = getImagesWithinExtent(target, + extent, level + 1, depth + 1); + lowerImages.addAll(drawableImages); + drawableImages = lowerImages; + } + + return drawableImages; + } + + /** + * Create tile images for the specified tiles + * + * @param target + * @param tilesToCreate + */ + protected void createTileImages(IGraphicsTarget target, + Collection tilesToCreate) { + for (Tile tile : tilesToCreate) { + if (jobMap.get(tile) == null) { + // No job already running for tile, create and schedule one + TileImageCreatorTask job = new TileImageCreatorTask(target, + tile); + jobMap.put(tile, job); + tileCreationPool.schedule(job); + } + } + } + + /** + * Adds a DrawableImage for the specified Tile. Disposes of any old image + * + * @param tile + * @param image + */ + public void addTileImage(Tile tile, DrawableImage image) { + DrawableImage oldImage = imageMap.put(tile, image); + if (oldImage != null) { + oldImage.dispose(); + } + Runnable task = jobMap.remove(tile); + if (task != null) { + tileCreationPool.cancel(task); + } + } + + /** + * Returns the raw image value from tile image that contains the lat/lon + * coordinate + * + * @param coordinate + * in lat/lon space + * @return + * @throws VizException + */ + public double interrogate(Coordinate coordinate) throws VizException { + try { + double[] local = new double[2]; + llToLocalProj + .transform(new double[] { coordinate.x, coordinate.y }, 0, + local, 0, 1); + double localX = local[0]; + double localY = local[1]; + + TileLevel level = tileSet.getTileLevel(lastPaintedLevel); + double[] grid = level.crsToGrid(localX, localY); + Tile tile = level.getTile(grid[0], grid[1]); + DrawableImage di = imageMap.get(tile); + if (di != null) { + IImage image = di.getImage(); + if (image instanceof IColormappedImage) { + return ((IColormappedImage) image).getValue((int) grid[0] + % tileSize, (int) grid[1] % tileSize); + } + } + } catch (TransformException e) { + throw new VizException("Error interrogating ", e); + } + return Double.NaN; + } +} diff --git a/cave/com.raytheon.uf.viz.d2d.ui/src/com/raytheon/uf/viz/d2d/ui/map/actions/ClearAction.java b/cave/com.raytheon.uf.viz.d2d.ui/src/com/raytheon/uf/viz/d2d/ui/map/actions/ClearAction.java index 5df6c34b9f..37aa7319b3 100644 --- a/cave/com.raytheon.uf.viz.d2d.ui/src/com/raytheon/uf/viz/d2d/ui/map/actions/ClearAction.java +++ b/cave/com.raytheon.uf.viz.d2d.ui/src/com/raytheon/uf/viz/d2d/ui/map/actions/ClearAction.java @@ -32,10 +32,12 @@ import com.raytheon.uf.viz.core.IDisplayPane; import com.raytheon.uf.viz.core.IDisplayPaneContainer; import com.raytheon.uf.viz.core.exception.VizException; import com.raytheon.uf.viz.core.globals.VizGlobalsManager; +import com.raytheon.uf.viz.core.maps.display.VizMapEditor; import com.raytheon.uf.viz.core.status.StatusConstants; import com.raytheon.uf.viz.d2d.ui.Activator; import com.raytheon.viz.ui.EditorUtil; import com.raytheon.viz.ui.HistoryList; +import com.raytheon.viz.ui.editor.AbstractEditor; import com.raytheon.viz.ui.editor.IMultiPaneEditor; /** @@ -60,7 +62,8 @@ import com.raytheon.viz.ui.editor.IMultiPaneEditor; * @version 1 */ public class ClearAction extends AbstractHandler { - private static final transient IUFStatusHandler statusHandler = UFStatus.getHandler(ClearAction.class); + private static final transient IUFStatusHandler statusHandler = UFStatus + .getHandler(ClearAction.class); @Override public Object execute(ExecutionEvent arg0) throws ExecutionException { @@ -69,15 +72,14 @@ public class ClearAction extends AbstractHandler { new NewMapEditor().execute(null); return null; } - - if (!(part instanceof IDisplayPaneContainer)) { - return null; - } - try { - HistoryList.getInstance().refreshLatestBundle(); - clear((IDisplayPaneContainer) part); - HistoryList.getInstance().addBundle(); + if (part instanceof IDisplayPaneContainer) { + HistoryList.getInstance().refreshLatestBundle(); + clear(part); + HistoryList.getInstance().addBundle(); + } else { + clear(part); + } } catch (VizException e) { throw new ExecutionException("Error during clear", e); } @@ -85,26 +87,19 @@ public class ClearAction extends AbstractHandler { return null; } - /** - * Clears the map - * - * The map is cleared with either the provided default-procedure.xml or a - * default world map, if no default-bundle.xml is available. - * - * @param target - * the graphics target - * @return the map descriptor prepared - * @throws VizException - */ - public static void clear(IDisplayPaneContainer container) - throws VizException { - if (container instanceof IMultiPaneEditor) { - ((IMultiPaneEditor) container).clear(); + public static void clear(IEditorPart part) throws VizException { + if (part instanceof VizMapEditor) { + ((VizMapEditor) part).clear(); + } else if (part instanceof AbstractEditor) { + // AbstractEditor asks the user if they are sure if we pass in + // save=true, if the user clicked clear than they must be sure so + // pass in save=false. + part.getSite().getPage().closeEditor(part, false); } else { - for (IDisplayPane displayPane : container.getDisplayPanes()) { - displayPane.clear(); - } + // Give other editors a chance to save. + part.getSite().getPage().closeEditor(part, true); } + if (EditorUtil.getActiveEditor() == null) { try { new NewMapEditor().execute(null); diff --git a/cave/com.raytheon.uf.viz.derivparam.python/src/com/raytheon/uf/viz/derivparam/python/MasterDerivScript.java b/cave/com.raytheon.uf.viz.derivparam.python/src/com/raytheon/uf/viz/derivparam/python/MasterDerivScript.java index 5cc4fd004a..f98f52cad2 100644 --- a/cave/com.raytheon.uf.viz.derivparam.python/src/com/raytheon/uf/viz/derivparam/python/MasterDerivScript.java +++ b/cave/com.raytheon.uf.viz.derivparam.python/src/com/raytheon/uf/viz/derivparam/python/MasterDerivScript.java @@ -385,11 +385,12 @@ public class MasterDerivScript extends PythonInterpreter { private void setDataRecordArg(String argName, IDataRecord argValue) throws JepException { boolean reshape = true; + long[] sizes = argValue.getSizes(); if (argValue instanceof FloatDataRecord) { FloatDataRecord record = (FloatDataRecord) argValue; - if (record.getDimension() == 2) { - jep.setNumeric(argName, record.getFloatData(), - (int) record.getSizes()[0], (int) record.getSizes()[1]); + if (sizes.length == 2) { + jep.setNumeric(argName, record.getFloatData(), (int) sizes[0], + (int) sizes[1]); reshape = false; } else { evaluateArgument(argName, record.getFloatData()); @@ -401,18 +402,18 @@ public class MasterDerivScript extends PythonInterpreter { jep.eval("del globals()['numpy']"); } else if (argValue instanceof IntegerDataRecord) { IntegerDataRecord record = (IntegerDataRecord) argValue; - if (record.getDimension() == 2) { - jep.setNumeric(argName, record.getIntData(), - (int) record.getSizes()[0], (int) record.getSizes()[1]); + if (sizes.length == 2) { + jep.setNumeric(argName, record.getIntData(), (int) sizes[0], + (int) sizes[1]); reshape = false; } else { evaluateArgument(argName, record.getIntData()); } } else if (argValue instanceof ByteDataRecord) { ByteDataRecord record = (ByteDataRecord) argValue; - if (record.getDimension() == 2) { - jep.setNumeric(argName, record.getByteData(), - (int) record.getSizes()[0], (int) record.getSizes()[1]); + if (sizes.length == 2) { + jep.setNumeric(argName, record.getByteData(), (int) sizes[0], + (int) sizes[1]); reshape = false; } else { evaluateArgument(argName, record.getByteData()); diff --git a/cave/com.raytheon.uf.viz.derivparam/src/com/raytheon/uf/viz/derivparam/inv/AbstractInventory.java b/cave/com.raytheon.uf.viz.derivparam/src/com/raytheon/uf/viz/derivparam/inv/AbstractInventory.java index 7046d034fa..c5079e13d2 100644 --- a/cave/com.raytheon.uf.viz.derivparam/src/com/raytheon/uf/viz/derivparam/inv/AbstractInventory.java +++ b/cave/com.raytheon.uf.viz.derivparam/src/com/raytheon/uf/viz/derivparam/inv/AbstractInventory.java @@ -171,11 +171,11 @@ public abstract class AbstractInventory implements DerivParamUpdateListener { protected Map> sourceAliases = new HashMap>(); - private List allSources; + protected List allSources; - private List allParameters; + protected List allParameters; - private List allLevels; + protected List allLevels; /** * A call to this method assigns the passed grid tree to the original grid diff --git a/cave/com.raytheon.uf.viz.kml.export.feature/.project b/cave/com.raytheon.uf.viz.kml.export.feature/.project new file mode 100644 index 0000000000..31141696dc --- /dev/null +++ b/cave/com.raytheon.uf.viz.kml.export.feature/.project @@ -0,0 +1,11 @@ + + + com.raytheon.uf.viz.kml.export.feature + + + + + + + + diff --git a/cave/com.raytheon.uf.viz.kml.export.feature/build.properties b/cave/com.raytheon.uf.viz.kml.export.feature/build.properties new file mode 100644 index 0000000000..64f93a9f0b --- /dev/null +++ b/cave/com.raytheon.uf.viz.kml.export.feature/build.properties @@ -0,0 +1 @@ +bin.includes = feature.xml diff --git a/cave/com.raytheon.uf.viz.kml.export.feature/feature.xml b/cave/com.raytheon.uf.viz.kml.export.feature/feature.xml new file mode 100644 index 0000000000..2a68be49e2 --- /dev/null +++ b/cave/com.raytheon.uf.viz.kml.export.feature/feature.xml @@ -0,0 +1,32 @@ + + + + + [Enter Feature Description here.] + + + + [Enter Copyright Description here.] + + + + [Enter License Description here.] + + + + + + + diff --git a/cave/com.raytheon.uf.viz.kml.export/.classpath b/cave/com.raytheon.uf.viz.kml.export/.classpath new file mode 100644 index 0000000000..ad32c83a78 --- /dev/null +++ b/cave/com.raytheon.uf.viz.kml.export/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/cave/com.raytheon.uf.viz.kml.export/.project b/cave/com.raytheon.uf.viz.kml.export/.project new file mode 100644 index 0000000000..ac1ac33ee2 --- /dev/null +++ b/cave/com.raytheon.uf.viz.kml.export/.project @@ -0,0 +1,28 @@ + + + com.raytheon.uf.viz.kml.export + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/cave/com.raytheon.uf.viz.kml.export/.settings/org.eclipse.jdt.core.prefs b/cave/com.raytheon.uf.viz.kml.export/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000000..9a05f5f33c --- /dev/null +++ b/cave/com.raytheon.uf.viz.kml.export/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +#Wed May 30 15:50:31 CDT 2012 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/cave/com.raytheon.uf.viz.kml.export/META-INF/MANIFEST.MF b/cave/com.raytheon.uf.viz.kml.export/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..f5ee20d888 --- /dev/null +++ b/cave/com.raytheon.uf.viz.kml.export/META-INF/MANIFEST.MF @@ -0,0 +1,27 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: KML Export +Bundle-SymbolicName: com.raytheon.uf.viz.kml.export;singleton:=true +Bundle-Version: 1.0.0.qualifier +Bundle-Activator: com.raytheon.uf.viz.kml.export.Activator +Eclipse-RegisterBuddy: com.raytheon.uf.common.serialization +Bundle-Vendor: RAYTHEON +Require-Bundle: org.eclipse.ui, + org.eclipse.core.runtime, + com.raytheon.uf.common.serialization;bundle-version="1.12.1174", + org.geotools;bundle-version="2.6.4", + com.raytheon.uf.viz.core;bundle-version="1.12.1174", + com.raytheon.uf.viz.core.rsc;bundle-version="1.0.0", + com.raytheon.uf.common.geospatial;bundle-version="1.12.1174", + com.raytheon.uf.common.colormap;bundle-version="1.12.1174", + com.raytheon.uf.common.time;bundle-version="1.12.1174", + com.raytheon.viz.ui;bundle-version="1.12.1174", + com.raytheon.uf.viz.core.maps;bundle-version="1.12.1174", + de.micromata.opengis.kml;bundle-version="1.0.0", + com.raytheon.viz.pointdata;bundle-version="1.12.1174", + com.raytheon.viz.radar;bundle-version="1.12.1174", + com.raytheon.uf.common.dataplugin.radar;bundle-version="1.0.0", + javax.measure;bundle-version="1.0.0", + javax.vecmath;bundle-version="1.3.1" +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Bundle-ActivationPolicy: lazy diff --git a/cave/com.raytheon.uf.viz.kml.export/build.properties b/cave/com.raytheon.uf.viz.kml.export/build.properties new file mode 100644 index 0000000000..e9863e281e --- /dev/null +++ b/cave/com.raytheon.uf.viz.kml.export/build.properties @@ -0,0 +1,5 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + plugin.xml diff --git a/cave/com.raytheon.uf.viz.kml.export/plugin.xml b/cave/com.raytheon.uf.viz.kml.export/plugin.xml new file mode 100644 index 0000000000..be0787b52b --- /dev/null +++ b/cave/com.raytheon.uf.viz.kml.export/plugin.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cave/com.raytheon.uf.viz.kml.export/src/com/raytheon/uf/viz/kml/export/Activator.java b/cave/com.raytheon.uf.viz.kml.export/src/com/raytheon/uf/viz/kml/export/Activator.java new file mode 100644 index 0000000000..565c20c8ce --- /dev/null +++ b/cave/com.raytheon.uf.viz.kml.export/src/com/raytheon/uf/viz/kml/export/Activator.java @@ -0,0 +1,50 @@ +package com.raytheon.uf.viz.kml.export; + +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.osgi.framework.BundleContext; + +/** + * The activator class controls the plug-in life cycle + */ +public class Activator extends AbstractUIPlugin { + + // The plug-in ID + public static final String PLUGIN_ID = "com.raytheon.uf.viz.kml.export"; //$NON-NLS-1$ + + // The shared instance + private static Activator plugin; + + /** + * The constructor + */ + public Activator() { + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext) + */ + public void start(BundleContext context) throws Exception { + super.start(context); + plugin = this; + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext) + */ + public void stop(BundleContext context) throws Exception { + plugin = null; + super.stop(context); + } + + /** + * Returns the shared instance + * + * @return the shared instance + */ + public static Activator getDefault() { + return plugin; + } + +} diff --git a/cave/com.raytheon.uf.viz.kml.export/src/com/raytheon/uf/viz/kml/export/KmlExportDialog.java b/cave/com.raytheon.uf.viz.kml.export/src/com/raytheon/uf/viz/kml/export/KmlExportDialog.java new file mode 100644 index 0000000000..da710ea2ad --- /dev/null +++ b/cave/com.raytheon.uf.viz.kml.export/src/com/raytheon/uf/viz/kml/export/KmlExportDialog.java @@ -0,0 +1,606 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.uf.viz.kml.export; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.layout.RowData; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Layout; +import org.eclipse.swt.widgets.MessageBox; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeItem; + +import com.raytheon.uf.viz.core.drawables.ResourcePair; +import com.raytheon.uf.viz.kml.export.KmlExportOptions.KmlExportTimeMode; +import com.raytheon.viz.ui.dialogs.CaveSWTDialog; + +/** + * Allow user to select options for export and starts the KmlExportJob. + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date         Ticket#    Engineer    Description
+ * ------------ ---------- ----------- --------------------------
+ * Jun 5, 2012            bsteffen     Initial creation
+ * 
+ * 
+ * + * @author bsteffen + * @version 1.0 + */ + +public class KmlExportDialog extends CaveSWTDialog { + + private final KmlExportOptions options; + + private Text locationText; + + private Tree productTree; + + private Button exportHiddenButton; + + private Button exportMapsButton; + + private Button shadeEarthButton; + + private Button setTimesButton; + + private Button timeSpanButton; + + private Button timeStampButton; + + private Button fillPlotsButton; + + private Button selectedFramesButton; + + private Button currentFramesButton; + + private Button allFramesButton; + + private Text framesFromText; + + private Text framesToText; + + public KmlExportDialog(Shell shell, KmlExportOptions options) { + super(shell, SWT.RESIZE | SWT.DIALOG_TRIM); + this.setText("Export KML"); + this.options = options; + } + + @Override + protected void initializeComponents(Shell shell) { + + Composite leftComposite = new Composite(shell, SWT.NONE); + leftComposite.setLayoutData(new GridData(SWT.NONE, SWT.FILL, false, + true)); + RowLayout layout = new RowLayout(SWT.VERTICAL); + layout.fill = true; + leftComposite.setLayout(layout); + + Group locationGroup = new Group(leftComposite, SWT.NONE); + initializeLocationGroup(locationGroup); + + Group framesGroup = new Group(leftComposite, SWT.NONE); + initializeFramesGroup(framesGroup); + + Group optionsGroup = new Group(leftComposite, SWT.NONE); + initializeOptionsGroup(optionsGroup); + + // Group timeOptionsGroup = new Group(leftComposite, SWT.NONE); + // initializeTimeOptionsGroup(timeOptionsGroup); + + Group productsGroup = new Group(shell, SWT.NONE); + GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true); + gridData.widthHint = 250; + productsGroup.setLayoutData(gridData); + initializeProductsGroup(productsGroup); + + Composite buttonComposite = new Composite(shell, SWT.NONE); + gridData = new GridData(SWT.FILL, SWT.NONE, true, false, 2, 1); + gridData.horizontalAlignment = SWT.CENTER; + buttonComposite.setLayoutData(gridData); + initializeButtons(buttonComposite); + + populateProductTree(); + if (productTree.getItemCount() == 0) { + // for the intial load change this option so there is something to + // export. + exportMapsButton.setSelection(true); + populateProductTree(); + } + + shell.pack(); + shell.setMinimumSize(shell.getSize()); + } + + @Override + protected Layout constructShellLayout() { + GridLayout mainLayout = new GridLayout(2, false); + mainLayout.marginHeight = 3; + mainLayout.marginWidth = 3; + return mainLayout; + } + + protected void initializeLocationGroup(Group group) { + group.setLayout(new GridLayout(2, false)); + group.setText("Export Location"); + locationText = new Text(group, SWT.BORDER); + GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true); + gridData.widthHint = 250; + locationText.setLayoutData(gridData); + locationText.setText(options.getKmzFileLocation().getAbsolutePath()); + Button button = new Button(group, SWT.PUSH); + button.setText("Browse ..."); + button.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent event) { + selectDestinationFile(); + } + }); + } + + protected void initializeFramesGroup(Group group) { + group.setLayout(new GridLayout(5, false)); + group.setText("Export Location"); + + allFramesButton = new Button(group, SWT.RADIO); + allFramesButton.setText("All Frames"); + GridData gridData = new GridData(); + gridData.horizontalSpan = 5; + allFramesButton.setLayoutData(gridData); + allFramesButton.setSelection(true); + + currentFramesButton = new Button(group, SWT.RADIO); + currentFramesButton.setText("Current Frame"); + gridData = new GridData(); + gridData.horizontalSpan = 5; + currentFramesButton.setLayoutData(gridData); + + selectedFramesButton = new Button(group, SWT.RADIO); + selectedFramesButton.setText("Frames"); + + new Label(group, SWT.NONE).setText("from:"); + framesFromText = new Text(group, SWT.BORDER); + gridData = new GridData(); + gridData.widthHint = 24; + framesFromText.setLayoutData(gridData); + framesFromText.setEnabled(false); + framesFromText.setText("1"); + new Label(group, SWT.NONE).setText("to:"); + framesToText = new Text(group, SWT.BORDER); + gridData = new GridData(); + gridData.widthHint = 24; + framesToText.setLayoutData(gridData); + framesToText.setEnabled(false); + int numFrames = 1; + for (KmlPane pane : options.getPanes()) { + int frames = pane.getDisplay().getDescriptor().getFramesInfo() + .getFrameCount(); + numFrames = Math.max(frames, numFrames); + } + framesToText.setText(Integer.toString(numFrames)); + selectedFramesButton.addSelectionListener(new SelectionAdapter() { + + @Override + public void widgetSelected(SelectionEvent e) { + framesToText.setEnabled(selectedFramesButton.getSelection()); + framesFromText.setEnabled(selectedFramesButton.getSelection()); + } + + }); + } + + protected void initializeOptionsGroup(Group group) { + group.setText("Other Options"); + group.setLayout(new RowLayout(SWT.VERTICAL)); + exportHiddenButton = new Button(group, SWT.CHECK); + exportHiddenButton.setText("Export Hidden"); + exportHiddenButton.setSelection(true); + exportHiddenButton + .setToolTipText("Include hidden products in the selection of products to export."); + exportHiddenButton.addSelectionListener(new SelectionAdapter() { + + @Override + public void widgetSelected(SelectionEvent e) { + populateProductTree(); + } + }); + exportMapsButton = new Button(group, SWT.CHECK); + exportMapsButton.setText("Export Maps"); + exportMapsButton.setSelection(false); + exportMapsButton + .setToolTipText("Include maps in the selection of products to export."); + exportMapsButton.addSelectionListener(new SelectionAdapter() { + + @Override + public void widgetSelected(SelectionEvent e) { + populateProductTree(); + } + }); + shadeEarthButton = new Button(group, SWT.CHECK); + shadeEarthButton.setText("Shade Earth"); + shadeEarthButton.setSelection(options.isShadeEarth()); + shadeEarthButton + .setToolTipText("Hides the Google Earth surface imagery."); + fillPlotsButton = new Button(group, SWT.CHECK); + fillPlotsButton.setText("Show Background Tiles"); + fillPlotsButton.setSelection(options.isFillPlotBackground()); + fillPlotsButton + .setToolTipText("Displays an opaque background tile behind point observations"); + } + + protected void initializeTimeOptionsGroup(Group group) { + group.setText("Time Options"); + group.setLayout(new RowLayout(SWT.VERTICAL)); + setTimesButton = new Button(group, SWT.CHECK); + setTimesButton.setText("Set KML Time"); + setTimesButton + .setSelection(options.getTimeMode() != KmlExportTimeMode.NONE); + setTimesButton + .setToolTipText("Causes Google Earth to display a time slider."); + Composite timeComposite = new Composite(group, SWT.NONE); + RowLayout layout = new RowLayout(SWT.VERTICAL); + layout.marginLeft = 20; + timeComposite.setLayout(layout); + timeSpanButton = new Button(timeComposite, SWT.RADIO); + timeSpanButton.setText("Time Span"); + timeSpanButton + .setSelection(options.getTimeMode() != KmlExportTimeMode.TIME_STAMP); + timeSpanButton.setEnabled(setTimesButton.getSelection()); + timeSpanButton + .setToolTipText("Allow products to be visible over a time range."); + timeStampButton = new Button(timeComposite, SWT.RADIO); + timeStampButton.setText("Time Stamp"); + timeStampButton + .setSelection(options.getTimeMode() == KmlExportTimeMode.TIME_STAMP); + timeStampButton.setEnabled(setTimesButton.getSelection()); + timeStampButton + .setToolTipText("Makes each product visible only at its exact valid time."); + setTimesButton.addSelectionListener(new SelectionAdapter() { + + @Override + public void widgetSelected(SelectionEvent e) { + timeSpanButton.setEnabled(setTimesButton.getSelection()); + timeStampButton.setEnabled(setTimesButton.getSelection()); + } + + }); + } + + protected void initializeProductsGroup(Group group) { + group.setText("Products"); + group.setLayout(new FillLayout()); + productTree = new Tree(group, SWT.CHECK); + productTree.addSelectionListener(new SelectionAdapter() { + + @Override + public void widgetSelected(SelectionEvent e) { + if (e.detail == SWT.CHECK) { + treeItemChecked((TreeItem) e.item); + } + super.widgetSelected(e); + } + + }); + } + + protected void initializeButtons(Composite comp) { + comp.setLayout(new RowLayout(SWT.HORIZONTAL)); + Button okButton = new Button(comp, SWT.PUSH); + okButton.setText("OK"); + okButton.setLayoutData(new RowData(100, SWT.DEFAULT)); + okButton.addSelectionListener(new SelectionAdapter() { + + @Override + public void widgetSelected(SelectionEvent e) { + okPressed(); + } + + }); + + Button cancelButton = new Button(comp, SWT.PUSH); + cancelButton.setText("Cancel"); + cancelButton.setLayoutData(new RowData(100, SWT.DEFAULT)); + cancelButton.addSelectionListener(new SelectionAdapter() { + + @Override + public void widgetSelected(SelectionEvent e) { + close(); + } + + }); + } + + protected void populateProductTree() { + boolean exportMaps = exportMapsButton.getSelection(); + boolean exportHidden = exportHiddenButton.getSelection(); + if (options.isSinglePane()) { + List rscList = options.getSinglPane().getResources( + exportMaps, exportHidden); + populateProductSubTree(rscList, null); + } else { + int index = 0; + for (KmlPane pane : options.getPanes()) { + List rscList = pane.getResources(exportMaps, + exportHidden); + TreeItem item = null; + if (index < productTree.getItemCount()) { + item = productTree.getItem(index); + } + if (!rscList.isEmpty()) { + if (item == null || item.getData() != pane) { + item = new TreeItem(productTree, SWT.NONE, index); + item.setText("Pane " + (index + 1)); + item.setData(pane); + populateProductSubTree(rscList, item); + item.setExpanded(true); + } else { + populateProductSubTree(rscList, item); + } + index += 1; + } else { + if (item != null && item.getData() == pane) { + item.dispose(); + } + } + } + } + } + + private void populateProductSubTree(List rscList, + final TreeItem parent) { + TreeItem[] items = parent != null ? parent.getItems() : productTree + .getItems(); + int itemIndex = 0; + int rscIndex = 0; + while (itemIndex < items.length || rscIndex < rscList.size()) { + TreeItem item = null; + if (itemIndex < items.length) { + item = items[itemIndex]; + } + ResourcePair pair = null; + if (rscIndex < rscList.size()) { + pair = rscList.get(rscIndex); + } + if (item != null && item.getData() == pair) { + itemIndex += 1; + rscIndex += 1; + } else if (item != null && !rscList.contains(item.getData())) { + item.dispose(); + itemIndex += 1; + } else { + if (parent != null) { + item = new TreeItem(parent, SWT.NONE, rscIndex); + } else { + item = new TreeItem(productTree, SWT.NONE, rscIndex); + } + String name = pair.getResource().getName(); + if (name == null) { + name = pair.getResource().getClass().getSimpleName(); + } + item.setText(name); + item.setData(pair); + item.setChecked(true); + treeItemChecked(item); + rscIndex += 1; + } + } + } + + private void treeItemChecked(TreeItem item) { + for (TreeItem ti : item.getItems()) { + ti.setChecked(item.getChecked()); + } + TreeItem parent = item.getParentItem(); + while (parent != null) { + parent.setChecked(true); + for (TreeItem ti : parent.getItems()) { + if (!ti.getChecked()) { + parent.setChecked(false); + break; + } + } + parent = parent.getParentItem(); + } + } + + protected void selectDestinationFile() { + FileDialog fileDialog = new FileDialog(this.shell, SWT.SAVE); + File file = new File(locationText.getText()); + fileDialog.setFileName(file.getName()); + if (file.getParentFile() != null && file.getParentFile().isDirectory()) { + fileDialog.setFilterPath(file.getParent()); + } + fileDialog.setFilterExtensions(new String[] { ".kmz" }); + fileDialog.open(); + + String filterPath = fileDialog.getFilterPath(); + String selectedFile = fileDialog.getFileName(); + /* + * Ensure that the user has entered a name for the file. + */ + if (selectedFile.equalsIgnoreCase("")) { + return; + } + + if (!filterPath.endsWith("/")) { + filterPath += "/"; + } + String destinationFile = filterPath + selectedFile; + this.locationText.setText(destinationFile); + this.locationText.setToolTipText(destinationFile); + } + + protected void okPressed() { + if (allFramesButton.getSelection()) { + options.setFirstFrameIndex(Integer.MIN_VALUE); + options.setLastFrameIndex(Integer.MAX_VALUE); + } else if (currentFramesButton.getSelection()) { + for (KmlPane pane : options.getPanes()) { + int frame = pane.getDisplay().getDescriptor().getFramesInfo() + .getFrameIndex(); + frame = Math.max(0, frame); + options.setFirstFrameIndex(frame); + options.setLastFrameIndex(frame + 1); + } + } else { + try { + int from = Integer.parseInt(framesFromText.getText()) - 1; + options.setFirstFrameIndex(from); + } catch (NumberFormatException e) { + MessageBox mb = new MessageBox(getShell(), SWT.ICON_ERROR + | SWT.OK); + mb.setText("Invalid Number"); + mb.setMessage(framesFromText.getText() + + " is not a valid number, please enter a valid number for the starting frame."); + mb.open(); + return; + } + try { + int to = Integer.parseInt(framesToText.getText()); + options.setLastFrameIndex(to); + } catch (NumberFormatException e) { + MessageBox mb = new MessageBox(getShell(), SWT.ICON_ERROR + | SWT.OK); + mb.setText("Invalid Number"); + mb.setMessage(framesFromText.getText() + + " is not a valid number, please enter a valid number for the ending frame."); + mb.open(); + return; + } + } + options.setKmzFileLocation(new File(locationText.getText())); + options.setShadeEarth(shadeEarthButton.getSelection()); + // if (!setTimesButton.getSelection()) { + // options.setTimeMode(KmlExportTimeMode.NONE); + // } else if (timeSpanButton.getSelection()) { + // options.setTimeMode(KmlExportTimeMode.TIME_SPAN); + // } else if (timeStampButton.getSelection()) { + // options.setTimeMode(KmlExportTimeMode.TIME_STAMP); + // + // } + options.setFillPlotBackground(fillPlotsButton.getSelection()); + if (options.isSinglePane()) { + List resourcesToExport = new ArrayList(); + for (TreeItem ti : productTree.getItems()) { + if (ti.getChecked()) { + resourcesToExport.add((ResourcePair) ti.getData()); + } + } + options.getSinglPane().setResourcesToExport(resourcesToExport); + } else { + for (TreeItem paneitem : productTree.getItems()) { + KmlPane pane = (KmlPane) paneitem.getData(); + List resourcesToExport = new ArrayList(); + for (TreeItem ti : paneitem.getItems()) { + if (ti.getChecked()) { + resourcesToExport.add((ResourcePair) ti.getData()); + } + } + pane.setResourcesToExport(resourcesToExport); + } + } + if (!validate()) { + // clear the current selection + for (KmlPane pane : options.getPanes()) { + pane.setResourcesToExport(null); + } + return; + } + new KmlExportJob(options).schedule(); + close(); + } + + protected boolean validate() { + boolean products = false; + for (KmlPane pane : options.getPanes()) { + if (pane.getResourcesToExport() != null + && !pane.getResourcesToExport().isEmpty()) { + products = true; + break; + } + } + if (!products) { + MessageBox mb = new MessageBox(getShell(), SWT.ICON_ERROR | SWT.OK); + mb.setText("No Products"); + mb.setMessage("No products are selected."); + mb.open(); + return false; + } + if (options.getFirstFrameIndex() > options.getLastFrameIndex() + || options.getLastFrameIndex() < 0) { + MessageBox mb = new MessageBox(getShell(), SWT.ICON_ERROR | SWT.OK); + mb.setText("Invalid Range"); + mb.setMessage("The frame range you entered is invalid, please enter a valid range"); + mb.open(); + return false; + } + File file = options.getKmzFileLocation(); + if (!file.getParentFile().exists()) { + MessageBox mb = new MessageBox(getShell(), SWT.ICON_QUESTION + | SWT.YES | SWT.NO); + mb.setText("Create Directory"); + mb.setMessage("The directory " + file.getParent() + + " does not exist, would you like to create it."); + int result = mb.open(); + if (result == SWT.YES) { + if (!file.getParentFile().mkdirs()) { + mb = new MessageBox(getShell(), SWT.ICON_ERROR | SWT.OK); + mb.setText("Error Creating Directory"); + mb.setMessage("An unspecified error has occured creating the directory, please select a new file location."); + mb.open(); + return false; + } + } else { + return false; + } + } + + if (file.exists()) { + MessageBox mb = new MessageBox(getShell(), SWT.ICON_WARNING + | SWT.YES | SWT.NO); + mb.setText("Overwrite file"); + mb.setMessage("The specified file already exist. Would you like to overwrite it?"); + int result = mb.open(); + if (result == SWT.NO) { + return false; + } + } + return true; + } + +} diff --git a/cave/com.raytheon.uf.viz.kml.export/src/com/raytheon/uf/viz/kml/export/KmlExportHandler.java b/cave/com.raytheon.uf.viz.kml.export/src/com/raytheon/uf/viz/kml/export/KmlExportHandler.java new file mode 100644 index 0000000000..087bcceca8 --- /dev/null +++ b/cave/com.raytheon.uf.viz.kml.export/src/com/raytheon/uf/viz/kml/export/KmlExportHandler.java @@ -0,0 +1,103 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.uf.viz.kml.export; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.ui.handlers.HandlerUtil; + +import com.raytheon.uf.viz.core.IDisplayPane; +import com.raytheon.uf.viz.core.IDisplayPaneContainer; +import com.raytheon.uf.viz.core.drawables.AbstractRenderableDisplay; +import com.raytheon.uf.viz.core.maps.display.MapRenderableDisplay; +import com.raytheon.uf.viz.kml.export.KmlExportOptions.KmlExportTimeMode; +import com.raytheon.viz.ui.EditorUtil; + +/** + * + * Handler for events from the KML Export menu item. + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date         Ticket#    Engineer    Description
+ * ------------ ---------- ----------- --------------------------
+ * Jun 1, 2012            bsteffen     Initial creation
+ * 
+ * 
+ * + * @author bsteffen + * @version 1.0 + */ +public class KmlExportHandler extends AbstractHandler { + + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + new KmlExportDialog(HandlerUtil.getActiveShell(event), + getDefaultOptions()).open(); + return null; + } + + protected static KmlExportOptions getDefaultOptions() { + KmlExportOptions options = new KmlExportOptions(); + options.setFirstFrameIndex(Integer.MIN_VALUE); + options.setLastFrameIndex(Integer.MAX_VALUE); + options.setPreserveVisibility(true); + options.setShadeEarth(false); + options.setTimeMode(KmlExportTimeMode.TIME_SPAN); + options.setKmzFileLocation(new File(System.getProperty("user.home"), + "caveExport.kmz")); + options.setPlotIconScale(3.5); + options.setFillPlotBackground(false); + options.setPaintSleepMillis(10); + options.setMaxRefreshSeconds(60); + IDisplayPaneContainer container = EditorUtil.getActiveVizContainer(); + List panes = new ArrayList(); + for (IDisplayPane pane : container.getDisplayPanes()) { + AbstractRenderableDisplay display = (AbstractRenderableDisplay) pane + .getRenderableDisplay(); + panes.add(new KmlPane(display, pane.getBounds())); + } + options.setPanes(panes); + return options; + } + + @Override + public void setEnabled(Object evaluationContext) { + IDisplayPaneContainer container = EditorUtil.getActiveVizContainer(); + if (container == null) { + super.setBaseEnabled(false); + return; + } + IDisplayPane pane = container.getActiveDisplayPane(); + if (pane == null) { + super.setBaseEnabled(false); + return; + } + super.setBaseEnabled(pane.getRenderableDisplay() instanceof MapRenderableDisplay); + } + +} diff --git a/cave/com.raytheon.uf.viz.kml.export/src/com/raytheon/uf/viz/kml/export/KmlExportJob.java b/cave/com.raytheon.uf.viz.kml.export/src/com/raytheon/uf/viz/kml/export/KmlExportJob.java new file mode 100644 index 0000000000..74a10c515a --- /dev/null +++ b/cave/com.raytheon.uf.viz.kml.export/src/com/raytheon/uf/viz/kml/export/KmlExportJob.java @@ -0,0 +1,676 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.uf.viz.kml.export; + +import java.awt.Graphics; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.TimeZone; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.swt.graphics.RGB; +import org.geotools.geometry.DirectPosition2D; +import org.geotools.referencing.GeodeticCalculator; +import org.opengis.referencing.FactoryException; +import org.opengis.referencing.datum.PixelInCell; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.referencing.operation.TransformException; + +import com.raytheon.uf.common.colormap.Color; +import com.raytheon.uf.common.colormap.IColorMap; +import com.raytheon.uf.common.geospatial.MapUtil; +import com.raytheon.uf.common.geospatial.TransformFactory; +import com.raytheon.uf.common.status.IUFStatusHandler; +import com.raytheon.uf.common.status.UFStatus; +import com.raytheon.uf.common.status.UFStatus.Priority; +import com.raytheon.uf.common.time.DataTime; +import com.raytheon.uf.viz.core.IExtent; +import com.raytheon.uf.viz.core.drawables.AbstractRenderableDisplay; +import com.raytheon.uf.viz.core.drawables.ColorMapParameters; +import com.raytheon.uf.viz.core.drawables.ColorMapParameters.LabelEntry; +import com.raytheon.uf.viz.core.drawables.IDescriptor; +import com.raytheon.uf.viz.core.drawables.IDescriptor.FramesInfo; +import com.raytheon.uf.viz.core.drawables.PaintProperties; +import com.raytheon.uf.viz.core.drawables.PaintStatus; +import com.raytheon.uf.viz.core.drawables.ResourcePair; +import com.raytheon.uf.viz.core.exception.VizException; +import com.raytheon.uf.viz.core.jobs.JobPool; +import com.raytheon.uf.viz.core.rsc.AbstractVizResource; +import com.raytheon.uf.viz.core.rsc.IResourceGroup; +import com.raytheon.uf.viz.core.rsc.ResourceList; +import com.raytheon.uf.viz.core.rsc.capabilities.BlendableCapability; +import com.raytheon.uf.viz.core.rsc.capabilities.BlendedCapability; +import com.raytheon.uf.viz.core.rsc.capabilities.ColorMapCapability; +import com.raytheon.uf.viz.kml.export.graphics.KmlGraphicsFactoryAdapter; +import com.raytheon.uf.viz.kml.export.graphics.KmlGraphicsTarget; +import com.raytheon.uf.viz.kml.export.io.KmlOutputManager; +import com.raytheon.uf.viz.kml.export.io.KmlRootOutputManager; + +import de.micromata.opengis.kml.v_2_2_0.AbstractObject; +import de.micromata.opengis.kml.v_2_2_0.Document; +import de.micromata.opengis.kml.v_2_2_0.Feature; +import de.micromata.opengis.kml.v_2_2_0.Folder; +import de.micromata.opengis.kml.v_2_2_0.LinearRing; +import de.micromata.opengis.kml.v_2_2_0.LookAt; +import de.micromata.opengis.kml.v_2_2_0.MultiGeometry; +import de.micromata.opengis.kml.v_2_2_0.Placemark; +import de.micromata.opengis.kml.v_2_2_0.PolyStyle; +import de.micromata.opengis.kml.v_2_2_0.ScreenOverlay; +import de.micromata.opengis.kml.v_2_2_0.Style; +import de.micromata.opengis.kml.v_2_2_0.TimePrimitive; +import de.micromata.opengis.kml.v_2_2_0.TimeSpan; +import de.micromata.opengis.kml.v_2_2_0.TimeStamp; +import de.micromata.opengis.kml.v_2_2_0.Units; +import de.micromata.opengis.kml.v_2_2_0.Vec2; + +/** + * The main Job for exporting KML in a background thread + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date         Ticket#    Engineer    Description
+ * ------------ ---------- ----------- --------------------------
+ * Jun 6, 2012            bsteffen     Initial creation
+ * 
+ * 
+ * + * @author bsteffen + * @version 1.0 + */ + +public class KmlExportJob extends Job { + private static final transient IUFStatusHandler statusHandler = UFStatus + .getHandler(KmlExportJob.class); + + private static final SimpleDateFormat KML_TIME_FORMAT = new SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + + private final KmlExportOptions options; + + private final JobPool backgroundPool = new JobPool("Exporting KML", 4, + true, Job.INTERACTIVE); + + public KmlExportJob(KmlExportOptions options) { + super("Generating Kml"); + setUser(true); + this.options = options; + KML_TIME_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT")); + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + SubMonitor smonitor = SubMonitor + .convert(monitor, "Generating KML", 800); + + try { + copyPanes(smonitor.newChild(20, SubMonitor.SUPPRESS_NONE)); + + initPanes(smonitor.newChild(80, SubMonitor.SUPPRESS_NONE)); + + KmlRootOutputManager out = new KmlRootOutputManager( + options.getKmzFileLocation()); + + exportPanes(smonitor.newChild(500, SubMonitor.SUPPRESS_NONE), out); + joinBackground(smonitor.newChild(150, SubMonitor.SUPPRESS_NONE)); + // Do not dispose until all background processes are done + for (KmlPane pane : options.getPanes()) { + pane.getDisplay().dispose(); + } + recursiveInvisibility(out.getContainer(), true); + out.close(); + smonitor.worked(50); + } catch (IOException e) { + statusHandler.handle(Priority.PROBLEM, "Error writing KML", e); + } + smonitor.done(); + return Status.OK_STATUS; + } + + /** + * Copy each pane and remove any panes that aren't being exported. + * + * @param monitor + */ + private void copyPanes(IProgressMonitor monitor) { + // Keep this code as fast as possible, if the user runs kml export in + // the background and modifies the main display it will affect kml if + // copy is not done. + List panes = options.getPanes(); + monitor.beginTask("Copying Displays", panes.size()); + Iterator paneIt = panes.iterator(); + while (paneIt.hasNext()) { + KmlPane pane = paneIt.next(); + List exports = pane.getResourcesToExport(); + if (exports == null || exports.isEmpty()) { + paneIt.remove(); + } else { + try { + AbstractRenderableDisplay display = pane.getDisplay(); + // copy the current time before clone + FramesInfo fi = display.getDescriptor().getFramesInfo(); + if (fi.getFrameTimes() != null) { + int index = fi.getFrameIndex(); + if (index > options.getFirstFrameIndex() + && index < options.getLastFrameIndex()) { + pane.setDisplayedTime(fi.getFrameTimes()[fi + .getFrameIndex()]); + } + } + display = display.cloneDisplay(); + pane.setDisplay(display); + KmlGraphicsFactoryAdapter graphicsAdapter = new KmlGraphicsFactoryAdapter( + display.getView().getExtent(), pane.getBounds()); + display.setGraphicsAdapter(graphicsAdapter); + KmlGraphicsTarget target = graphicsAdapter.constructTarget( + null, 0.0f, 0.0f); + target.setBackgroundColor(display.getBackgroundColor()); + display.setup(target); + pane.setTarget(target); + } catch (VizException e) { + statusHandler.handle(Priority.PROBLEM, + e.getLocalizedMessage(), e); + paneIt.remove(); + } + } + monitor.worked(1); + } + monitor.done(); + } + + private void initPanes(IProgressMonitor monitor) { + List panes = options.getPanes(); + monitor.beginTask("Initializing Displays", panes.size()); + Iterator paneIt = panes.iterator(); + while (paneIt.hasNext()) { + KmlPane pane = paneIt.next(); + try { + AbstractRenderableDisplay display = pane.getDisplay(); + + IDescriptor descriptor = display.getDescriptor(); + descriptor.setRenderableDisplay(display); + descriptor.getResourceList().instantiateResources(descriptor, + true); + for (ResourcePair rp : descriptor.getResourceList()) { + rp.getResource().init(pane.getTarget()); + monitor.worked(1); + } + } catch (VizException e) { + statusHandler.handle(Priority.PROBLEM, e.getLocalizedMessage(), + e); + paneIt.remove(); + } + monitor.worked(1); + } + monitor.done(); + } + + private void exportPanes(IProgressMonitor monitor, KmlOutputManager out) + throws IOException { + List panes = options.getPanes(); + SubMonitor smonitor = SubMonitor.convert(monitor, "Exporting Displays", + panes.size() * 100); + int paneNumber = 1; + for (KmlPane pane : options.getPanes()) { + KmlOutputManager displayOut = out; + if (!options.isSinglePane()) { + displayOut = out.createFolder("Pane " + (paneNumber++)); + } + AbstractRenderableDisplay display = pane.getDisplay(); + setView(pane, displayOut); + if (options.isShadeEarth()) { + shadeEarth(displayOut, display.getBackgroundColor()); + } + IDescriptor descriptor = display.getDescriptor(); + List exports = new ArrayList(); + for (ResourcePair rp : descriptor.getResourceList()) { + if (pane.getResourcesToExport().contains(rp)) { + exports.add(rp); + } else { + rp.getProperties().setVisible(false); + } + } + exportResources(smonitor.newChild(100, SubMonitor.SUPPRESS_NONE), + displayOut, exports, pane); + if (smonitor.isCanceled()) { + break; + } + } + smonitor.done(); + } + + private void exportResources(IProgressMonitor monitor, + KmlOutputManager out, List exports, KmlPane pane) + throws IOException { + SubMonitor smonitor = SubMonitor.convert(monitor, "Exporting Products", + exports.size() * 100); + KmlGraphicsTarget target = pane.getTarget(); + AbstractRenderableDisplay display = pane.getDisplay(); + IDescriptor descriptor = display.getDescriptor(); + List visibility = new ArrayList(); + for (ResourcePair rp : exports) { + visibility.add(rp.getProperties().isVisible()); + rp.getProperties().setVisible(false); + } + for (int c = 0; c < exports.size(); c++) { + ResourcePair rp = exports.get(c); + AbstractVizResource rsc = rp.getResource(); + rp.getProperties().setVisible(true); + String name = rp.getResource().getName(); + if (name == null) { + name = rp.getResource().getClass().getSimpleName(); + } + KmlOutputManager resourceOut = out.createFolder(name.trim()); + SubMonitor rscmonitor = smonitor.newChild(100, + SubMonitor.SUPPRESS_NONE); + if (rsc.hasCapability(BlendableCapability.class)) { + ResourceList list = rsc + .getCapability(BlendableCapability.class) + .getResourceList(); + exportResources(rscmonitor, resourceOut, list, pane); + } else { + int startIndex = options.getFirstFrameIndex(); + startIndex = Math.max(startIndex, 0); + int lastIndex = options.getLastFrameIndex(); + lastIndex = Math.min(lastIndex, descriptor.getNumberOfFrames()); + rscmonitor.beginTask("Saving " + rsc.getName(), lastIndex + - startIndex); + addColorMap(resourceOut, display.getBackgroundColor(), rsc); + DataTime[] times = descriptor.getFramesInfo().getTimeMap() + .get(rsc); + if ((times == null || times.length == 0) + && rsc instanceof IResourceGroup) { + ResourceList list = ((IResourceGroup) rsc) + .getResourceList(); + for (ResourcePair pair : list) { + times = descriptor.getFramesInfo().getTimeMap() + .get(pair.getResource()); + if (times != null && times.length > 0) { + break; + } + } + } + List pastFrames = new ArrayList(); + for (int i = startIndex; i < lastIndex; i += 1) { + descriptor.setFramesInfo(new FramesInfo(i)); + KmlOutputManager timeOut = resourceOut; + if (rsc.isTimeAgnostic() + && (times == null || times.length == 0)) { + i = lastIndex - 1; + } else { + if (i < 0 || times == null || i >= times.length) { + rscmonitor.worked(1); + continue; + } + DataTime time = times[i]; + if (time == null || pastFrames.contains(time)) { + rscmonitor.worked(1); + continue; + } + timeOut = resourceOut.createFolder(time + .getLegendString()); + timeOut.getContainer().setTimePrimitive( + getTimePrimitive(times, i)); + pastFrames.add(time); + } + PaintProperties paintProps = new PaintProperties(1.0f, + (float) display.getZoom(), display.getView(), + pane.getBounds(), false, descriptor.getFramesInfo()); + paintResource(rscmonitor, timeOut, display, target, rsc, + paintProps); + rscmonitor.worked(1); + if (rscmonitor.isCanceled()) { + return; + } + } + } + rp.getProperties().setVisible(false); + if (options.isPreserveVisibility() && !visibility.get(c)) { + resourceOut.getContainer().setVisibility(false); + } + rscmonitor.done(); + } + } + + private void setView(KmlPane pane, KmlOutputManager out) { + IExtent extent = pane.getDisplay().getView().getExtent(); + try { + DirectPosition2D center = new DirectPosition2D( + extent.getCenter()[0], extent.getCenter()[1]); + DirectPosition2D corner = new DirectPosition2D(extent.getMaxX(), + extent.getMinX()); + MathTransform gridToLatLon = TransformFactory.gridToLatLon(pane + .getDisplay().getDescriptor().getGridGeometry(), + PixelInCell.CELL_CENTER); + gridToLatLon.transform(center, center); + gridToLatLon.transform(corner, corner); + + GeodeticCalculator gc = new GeodeticCalculator(); + gc.setStartingGeographicPoint(MapUtil.correctLon(center.x), + center.y); + gc.setDestinationGeographicPoint(MapUtil.correctLon(corner.x), + corner.y); + LookAt lookAt = out.getContainer().createAndSetLookAt(); + lookAt.setLongitude(center.x); + lookAt.setLatitude(center.y); + lookAt.setRange(gc.getOrthodromicDistance()); + if (pane.getDisplayedTime() != null) { + DataTime time = pane.getDisplayedTime(); + TimeStamp ts = new TimeStamp(); + ts.setWhen(KML_TIME_FORMAT.format(new Date(time.getMatchValid()))); + // At the time of this writing the current api doesn't allow + // setting time primitive for AbstractView + lookAt.setAbstractViewObjectExtension(Arrays + .asList((AbstractObject) ts)); + } + } catch (TransformException e) { + statusHandler.handle(Priority.PROBLEM, e.getLocalizedMessage(), e); + } catch (FactoryException e) { + statusHandler.handle(Priority.PROBLEM, e.getLocalizedMessage(), e); + } + } + + /** + * Given a list of times for a resource and the index of the current time, + * generate a KML TimePrimitive for a resource. When the time mode is SPAN + * the times in the array are used to calculate a span such that the spans + * for every time create a continuos time line. + * + * @param times + * @param index + * @return + */ + private TimePrimitive getTimePrimitive(DataTime[] times, int index) { + long validTime = times[index].getMatchValid(); + switch (options.getTimeMode()) { + case TIME_STAMP: { + TimeStamp ts = new TimeStamp(); + ts.setWhen(KML_TIME_FORMAT.format(new Date(validTime))); + return ts; + } + case TIME_SPAN: { + long prevValid = 0; + long nextValid = 0; + for (DataTime t : times) { + if (t == null) { + continue; + } + long valid = t.getMatchValid(); + if (valid < validTime) { + if (prevValid == 0 || prevValid < valid) { + prevValid = valid; + } + } else if (valid > validTime) { + if (nextValid == 0 || nextValid > valid) { + nextValid = valid; + } + } + } + long prevDist = 0; + long nextDist = 0; + if (prevValid != 0) { + nextDist = prevDist = (validTime - prevValid) / 2; + } + if (nextValid != 0) { + nextDist = (nextValid - validTime) / 2; + if (prevDist == 0) { + prevDist = nextDist; + } + } + TimeSpan span = new TimeSpan(); + span.setBegin(KML_TIME_FORMAT + .format(new Date(validTime - prevDist))); + span.setEnd(KML_TIME_FORMAT.format(new Date(validTime + nextDist))); + return span; + } + default: + return null; + } + } + + /** + * KML reference documentation from google clearly states that a feature is + * visible only if all of it's ancestors are also visible. Google + * Earth(tested on version 6.2) ignores this and displays everything as + * visible unless that item is specifically set to invisible even when + * ancestors are invisible. This function makes google earth work properly + * by finding invisible features and making all their children invisible. + * + * @param feature + * @param parentVisibility + */ + private void recursiveInvisibility(Feature feature, boolean parentVisibility) { + if (!parentVisibility) { + feature.setVisibility(false); + } + List features = null; + if (feature instanceof Folder) { + features = ((Folder) feature).getFeature(); + } else if (feature instanceof Document) { + features = ((Document) feature).getFeature(); + } + if (features == null) { + return; + } + for (Feature f : features) { + if (f == null) { + continue; + } + recursiveInvisibility(f, + !Boolean.FALSE.equals(feature.isVisibility())); + } + } + + private void paintResource(IProgressMonitor monitor, KmlOutputManager out, + AbstractRenderableDisplay display, KmlGraphicsTarget target, + AbstractVizResource resource, PaintProperties paintProps) { + target.setNeedsRefresh(true); + long startTime = System.currentTimeMillis(); + while (target.isNeedsRefresh() + || resource.getPaintStatus() != PaintStatus.PAINTED) { + if (target.isNeedsRefresh()) { + target.beginFrame(paintProps.getView(), false); + try { + display.paint(target, paintProps); + } catch (VizException e) { + statusHandler.handle(Priority.PROBLEM, + e.getLocalizedMessage(), e); + return; + } + target.endFrame(); + } + if (System.currentTimeMillis() - startTime > options + .getMaxRefreshSeconds() * 1000) { + statusHandler.handle(Priority.PROBLEM, resource.getName() + + " took more than " + options.getMaxRefreshSeconds() + + " seconds to paint, KML may be incomplete."); + break; + } + try { + Thread.sleep(options.getPaintSleepMillis()); + } catch (InterruptedException e) { + } + if (monitor.isCanceled()) { + break; + } + } + List generators = new ArrayList( + target.getGenerators()); + for (KmlFeatureGenerator generator : generators) { + generator + .setGridGeometry(display.getDescriptor().getGridGeometry()); + generator.setBackgroundColor(display.getBackgroundColor()); + generator.setOptions(options); + } + backgroundPool.schedule(new GenerateRunnable(generators, out)); + } + + private void addColorMap(KmlOutputManager out, RGB backcolor, + AbstractVizResource rsc) throws IOException { + ColorMapParameters parameters = null; + if (rsc.hasCapability(ColorMapCapability.class)) { + ColorMapCapability cap = rsc + .getCapability(ColorMapCapability.class); + parameters = cap.getColorMapParameters(); + } else { + return; + } + double xAnchor = 0; + if (rsc.hasCapability(BlendedCapability.class)) { + BlendedCapability cap = rsc.getCapability(BlendedCapability.class); + xAnchor = Math.min(1, cap.getResourceIndex()); + } + IColorMap colorMap = parameters.getColorMap(); + BufferedImage bi = new BufferedImage(colorMap.getSize() * 2, 25, + BufferedImage.TYPE_INT_RGB); + Graphics graphics = bi.getGraphics(); + graphics.setColor(new java.awt.Color(backcolor.red, backcolor.green, + backcolor.blue)); + graphics.fillRect(0, 0, bi.getWidth(), 25); + int x = 0; + for (Color color : colorMap.getColors()) { + graphics.setColor(new java.awt.Color(color.getRed(), color + .getGreen(), color.getBlue(), color.getAlpha())); + graphics.drawLine(x, 0, x, 25); + x += 1; + graphics.drawLine(x, 0, x, 25); + x += 1; + } + for (LabelEntry label : parameters.getLabels()) { + if (label.getText().isEmpty()) { + continue; + } + Rectangle2D bounds = graphics.getFontMetrics().getStringBounds( + label.getText(), graphics); + int centerX = (int) (bi.getWidth() * label.getLocation()); + int leftX = (int) (centerX - bounds.getWidth() / 2); + if (leftX < 0) { + leftX = 0; + } else if (leftX + bounds.getWidth() > bi.getWidth()) { + leftX = (int) (bi.getWidth() - bounds.getWidth()); + } + graphics.setColor(java.awt.Color.BLACK); + graphics.fillRect(leftX - 1, 2, (int) bounds.getWidth() + 2, + (int) bounds.getHeight() + 2); + graphics.setColor(java.awt.Color.WHITE); + graphics.drawString(label.getText(), leftX, + (int) bounds.getHeight() + 1); + } + graphics.dispose(); + ScreenOverlay overlay = new ScreenOverlay(); + overlay.setName("ColorMap"); + Vec2 overlayxy = overlay.createAndSetOverlayXY(); + overlayxy.withX(xAnchor).withXunits(Units.FRACTION); + overlayxy.withY(1).withYunits(Units.FRACTION); + Vec2 screenxy = overlay.createAndSetScreenXY(); + screenxy.withX(xAnchor).withXunits(Units.FRACTION); + screenxy.withY(1).withYunits(Units.FRACTION); + overlay.createAndSetIcon().setHref( + out.addImage(bi, "colormap" + xAnchor + ".png")); + out.addFeature(overlay); + } + + private void shadeEarth(KmlOutputManager out, RGB color) { + Placemark placemark = new Placemark(); + placemark.setName("Background Color"); + Style style = new Style(); + style.createAndSetIconStyle().setScale(0.0); + PolyStyle polyStyle = style.createAndSetPolyStyle(); + polyStyle.setFill(true); + polyStyle.setOutline(false); + polyStyle.setColor(KmlFeatureGenerator.toColorStr(1.0, color)); + placemark.setStyleUrl(out.getStyleUrl(style)); + // Google earth seems to do a weird things with one big polygon when you + // zoom way out, specifically there is lots of flickering and it misses + // big pieces towards the back of the sphere, lots of smaller polygons + // helps avoid the missing hunks but I still see a lot of flickering. + MultiGeometry multi = placemark.createAndSetMultiGeometry(); + for (int i = -180; i < 180; i += 10) { + for (int j = -90; j < 90; j += 10) { + LinearRing ring = multi.createAndAddPolygon() + .createAndSetOuterBoundaryIs().createAndSetLinearRing(); + ring.addToCoordinates(i, j); + ring.addToCoordinates(i, j + 10); + ring.addToCoordinates(i + 10, j + 10); + ring.addToCoordinates(i + 10, j); + ring.addToCoordinates(i, j); + } + + } + out.addFeature(placemark); + } + + private void joinBackground(IProgressMonitor monitor) { + // some tasks(like radar mosaic) can take a very long time to finish the + // background task, so this waits for those to finish and makes an + // attempt to let the user know how it is going. + int remaining = backgroundPool.getWorkRemaining(); + monitor.beginTask("Finalizing KML", remaining); + while (remaining > 0) { + try { + Thread.sleep(300); + } catch (InterruptedException e) { + } + int r = backgroundPool.getWorkRemaining(); + monitor.worked(remaining - r); + remaining = r; + if (monitor.isCanceled()) { + monitor.subTask("Canceling"); + backgroundPool.cancel(); + break; + } + } + backgroundPool.join(); + } + + private static class GenerateRunnable implements Runnable { + + private final List generators; + + private final KmlOutputManager outputManager; + + public GenerateRunnable(List generators, + KmlOutputManager outputManager) { + this.generators = generators; + this.outputManager = outputManager; + } + + @Override + public void run() { + for (KmlFeatureGenerator generator : generators) { + generator.addFeature(outputManager); + } + } + + } + +} diff --git a/cave/com.raytheon.uf.viz.kml.export/src/com/raytheon/uf/viz/kml/export/KmlExportOptions.java b/cave/com.raytheon.uf.viz.kml.export/src/com/raytheon/uf/viz/kml/export/KmlExportOptions.java new file mode 100644 index 0000000000..e1f98cc73c --- /dev/null +++ b/cave/com.raytheon.uf.viz.kml.export/src/com/raytheon/uf/viz/kml/export/KmlExportOptions.java @@ -0,0 +1,172 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.uf.viz.kml.export; + +import java.io.File; +import java.util.List; + +/** + * Contains any options which can be configured for KML export. + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date         Ticket#    Engineer    Description
+ * ------------ ---------- ----------- --------------------------
+ * Jun 4, 2012            bsteffen     Initial creation
+ * 
+ * 
+ * + * @author bsteffen + * @version 1.0 + */ + +public class KmlExportOptions { + + public enum KmlExportTimeMode { + NONE, TIME_STAMP, TIME_SPAN; + } + + private File kmzFileLocation; + + private int firstFrameIndex; + + private int lastFrameIndex; + + private boolean shadeEarth; + + // Google Earth requires a fairly large plot scale to make plots look nice + // but World Wind prefers a smaller scale. + private double plotIconScale; + + private boolean fillPlotBackground; + + private boolean preserveVisibility; + + private KmlExportTimeMode timeMode; + + private List panes; + + private int paintSleepMillis; + + private int maxRefreshSeconds; + + public File getKmzFileLocation() { + return kmzFileLocation; + } + + public void setKmzFileLocation(File kmzFileLocation) { + this.kmzFileLocation = kmzFileLocation; + } + + public int getFirstFrameIndex() { + return firstFrameIndex; + } + + public void setFirstFrameIndex(int firstFrameIndex) { + this.firstFrameIndex = firstFrameIndex; + } + + public int getLastFrameIndex() { + return lastFrameIndex; + } + + public void setLastFrameIndex(int lastFrameIndex) { + this.lastFrameIndex = lastFrameIndex; + } + + public boolean isShadeEarth() { + return shadeEarth; + } + + public void setShadeEarth(boolean shadeEarth) { + this.shadeEarth = shadeEarth; + } + + public boolean isFillPlotBackground() { + return fillPlotBackground; + } + + public void setFillPlotBackground(boolean fillPlotBackground) { + this.fillPlotBackground = fillPlotBackground; + } + + public boolean isPreserveVisibility() { + return preserveVisibility; + } + + public void setPreserveVisibility(boolean preserveVisibility) { + this.preserveVisibility = preserveVisibility; + } + + public KmlExportTimeMode getTimeMode() { + return timeMode; + } + + public void setTimeMode(KmlExportTimeMode timeMode) { + this.timeMode = timeMode; + } + + public List getPanes() { + return panes; + } + + public void setPanes(List panes) { + this.panes = panes; + } + + public boolean isSinglePane() { + return panes != null && panes.size() == 1; + } + + public KmlPane getSinglPane() { + if (isSinglePane()) { + return panes.get(0); + } else { + return null; + } + } + + public int getPaintSleepMillis() { + return paintSleepMillis; + } + + public void setPaintSleepMillis(int paintSleepMillis) { + this.paintSleepMillis = paintSleepMillis; + } + + public int getMaxRefreshSeconds() { + return maxRefreshSeconds; + } + + public void setMaxRefreshSeconds(int maxRefreshSeconds) { + this.maxRefreshSeconds = maxRefreshSeconds; + } + + public double getPlotIconScale() { + return plotIconScale; + } + + public void setPlotIconScale(double plotIconScale) { + this.plotIconScale = plotIconScale; + } + +} diff --git a/cave/com.raytheon.uf.viz.kml.export/src/com/raytheon/uf/viz/kml/export/KmlFeatureGenerator.java b/cave/com.raytheon.uf.viz.kml.export/src/com/raytheon/uf/viz/kml/export/KmlFeatureGenerator.java new file mode 100644 index 0000000000..5bdb52ba34 --- /dev/null +++ b/cave/com.raytheon.uf.viz.kml.export/src/com/raytheon/uf/viz/kml/export/KmlFeatureGenerator.java @@ -0,0 +1,111 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.uf.viz.kml.export; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.swt.graphics.RGB; +import org.geotools.coverage.grid.GeneralGridGeometry; +import org.geotools.coverage.grid.GridGeometry2D; +import org.opengis.referencing.FactoryException; +import org.opengis.referencing.datum.PixelInCell; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.referencing.operation.TransformException; + +import com.raytheon.uf.common.geospatial.TransformFactory; +import com.raytheon.uf.viz.kml.export.io.KmlOutputManager; + +import de.micromata.opengis.kml.v_2_2_0.Coordinate; + +/** + * Anything that can be drawn on the screen can also be used to create a KML + * feature, this class provides some basic utility functions as well as an + * interface for classes that generate KML. + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date         Ticket#    Engineer    Description
+ * ------------ ---------- ----------- --------------------------
+ * Jun 14, 2012            bsteffen     Initial creation
+ * 
+ * 
+ * + * @author bsteffen + * @version 1.0 + */ + +public abstract class KmlFeatureGenerator { + + protected GridGeometry2D gridGeometry; + + protected MathTransform gridToLatLon; + + protected RGB backgroundColor; + + protected KmlExportOptions options; + + public void setGridGeometry(GeneralGridGeometry gridGeometry) { + this.gridGeometry = GridGeometry2D.wrap(gridGeometry); + } + + public void setBackgroundColor(RGB backgroundColor) { + this.backgroundColor = backgroundColor; + } + + public void setOptions(KmlExportOptions options) { + this.options = options; + } + + public Coordinate transformToLatLon(double gridX, double gridY) + throws TransformException, FactoryException { + return transformToLatLon(new double[] { gridX, gridY }); + } + + public Coordinate transformToLatLon(double[] gridPixel) + throws TransformException, FactoryException { + if (gridToLatLon == null) { + gridToLatLon = TransformFactory.gridToLatLon(gridGeometry, + PixelInCell.CELL_CENTER); + } + double[] out = new double[2]; + gridToLatLon.transform(gridPixel, 0, out, 0, 1); + return new Coordinate(out[0], out[1]); + } + + public List transformToLatLon(List gridPixels) + throws TransformException, FactoryException { + List result = new ArrayList(); + for (double[] gridPixel : gridPixels) { + result.add(transformToLatLon(gridPixel)); + } + return result; + } + + public abstract void addFeature(KmlOutputManager outputManager); + + public static String toColorStr(double alpha, RGB rgb) { + return String.format("%02x%02x%02x%02x", (int) (alpha * 255), rgb.blue, + rgb.green, rgb.red); + } + +} diff --git a/cave/com.raytheon.uf.viz.kml.export/src/com/raytheon/uf/viz/kml/export/KmlPane.java b/cave/com.raytheon.uf.viz.kml.export/src/com/raytheon/uf/viz/kml/export/KmlPane.java new file mode 100644 index 0000000000..a889a562d3 --- /dev/null +++ b/cave/com.raytheon.uf.viz.kml.export/src/com/raytheon/uf/viz/kml/export/KmlPane.java @@ -0,0 +1,123 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.uf.viz.kml.export; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.swt.graphics.Rectangle; + +import com.raytheon.uf.common.time.DataTime; +import com.raytheon.uf.viz.core.drawables.AbstractRenderableDisplay; +import com.raytheon.uf.viz.core.drawables.ResourcePair; +import com.raytheon.uf.viz.kml.export.graphics.KmlGraphicsTarget; + +/** + * VizDisplayPane but for KML! + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date         Ticket#    Engineer    Description
+ * ------------ ---------- ----------- --------------------------
+ * Jun 5, 2012            bsteffen     Initial creation
+ * 
+ * 
+ * + * @author bsteffen + * @version 1.0 + */ + +public class KmlPane { + + private List resourcesToExport; + + private AbstractRenderableDisplay display; + + private Rectangle bounds; + + private KmlGraphicsTarget target; + + private DataTime displayedTime; + + public KmlPane(AbstractRenderableDisplay display, Rectangle bounds) { + this.display = display; + this.bounds = bounds; + } + + public List getResources(boolean includeMaps, + boolean includeHidden) { + List rscList = new ArrayList(); + for (ResourcePair rp : display.getDescriptor().getResourceList()) { + if (!rp.getResourceData().equals(rp.getResourceData())) { + // A special check for those special resources which will never + // work with KML because they don't properly implement equals. + // ... like GFE + continue; + } else if (rp.getProperties().isSystemResource()) { + continue; + } else if (!includeMaps && rp.getProperties().isMapLayer()) { + continue; + } else if (!includeHidden && !rp.getProperties().isVisible()) { + continue; + } + rscList.add(rp); + } + return rscList; + } + + public void setDisplay(AbstractRenderableDisplay display) { + this.display = display; + } + + public void setTarget(KmlGraphicsTarget target) { + this.target = target; + } + + public List getResourcesToExport() { + return resourcesToExport; + } + + public void setResourcesToExport(List resourcesToExport) { + this.resourcesToExport = resourcesToExport; + } + + public AbstractRenderableDisplay getDisplay() { + return display; + } + + public Rectangle getBounds() { + return bounds; + } + + public KmlGraphicsTarget getTarget() { + return target; + } + + public DataTime getDisplayedTime() { + return displayedTime; + } + + public void setDisplayedTime(DataTime displayedTime) { + this.displayedTime = displayedTime; + } + +} diff --git a/cave/com.raytheon.uf.viz.kml.export/src/com/raytheon/uf/viz/kml/export/graphics/KmlFont.java b/cave/com.raytheon.uf.viz.kml.export/src/com/raytheon/uf/viz/kml/export/graphics/KmlFont.java new file mode 100644 index 0000000000..51883cc759 --- /dev/null +++ b/cave/com.raytheon.uf.viz.kml.export/src/com/raytheon/uf/viz/kml/export/graphics/KmlFont.java @@ -0,0 +1,185 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.uf.viz.kml.export.graphics; + +import java.awt.Font; +import java.awt.FontFormatException; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.raytheon.uf.viz.core.drawables.IFont; + +/** + * + * KML has really bad font support so only support the minimum operations + * required to avoid errors. + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date         Ticket#    Engineer    Description
+ * ------------ ---------- ----------- --------------------------
+ * Jun 1, 2012            bsteffen     Initial creation
+ * 
+ * 
+ * + * @author bsteffen + * @version 1.0 + */ +public class KmlFont implements IFont { + + private Font font; + + private float magnification; + + private boolean scaleFont; + + private boolean smoothing; + + public KmlFont(Font font) { + this.font = font; + this.magnification = 1.0f; + } + + public KmlFont() { + this(new Font(java.awt.Font.MONOSPACED, Font.BOLD, 14)); + } + + public KmlFont(String fontName) { + this(new Font(fontName, Font.PLAIN, 10)); + + } + + public KmlFont(String fontName, float fontSize) { + this(new Font(fontName, Font.PLAIN, (int) fontSize)); + } + + public KmlFont(String fontName, float fontSize, Style[] styles) { + this(new Font(fontName, toAwtStyle(styles), (int) fontSize)); + } + + public KmlFont(File fontFile, float fontSize, Style[] styles) + throws FontFormatException, IOException { + this(Font.createFont(Font.TRUETYPE_FONT, fontFile).deriveFont(fontSize) + .deriveFont(toAwtStyle(styles))); + } + + @Override + public String getFontName() { + return this.font.getFontName(); + } + + @Override + public float getFontSize() { + return this.font.getSize2D(); + } + + @Override + public Style[] getStyle() { + return toVizStyles(font.getStyle()); + } + + @Override + public void dispose() { + + } + + @Override + public IFont deriveWithSize(float size) { + return new KmlFont(font.deriveFont(size)); + } + + @Override + public void setMagnification(float magnification) { + setMagnification(magnification, true); + } + + @Override + public void setMagnification(float magnification, boolean scaleFont) { + if (scaleFont) { + this.font = font.deriveFont(font.getSize2D() * magnification); + } else { + this.magnification = magnification; + } + } + + @Override + public float getMagnification() { + return magnification; + } + + @Override + public void setSmoothing(boolean smooth) { + this.smoothing = smooth; + } + + @Override + public boolean getSmoothing() { + return smoothing; + } + + @Override + public boolean isScaleFont() { + return scaleFont; + } + + @Override + public void setScaleFont(boolean scaleFont) { + this.scaleFont = scaleFont; + } + + public Font getFont() { + return font; + } + + public void setFont(Font font) { + this.font = font; + } + + private static int toAwtStyle(Style[] styles) { + int styleInt = Font.PLAIN; + if (styles == null || styles.length == 0) { + return styleInt; + } + for (Style style : styles) { + if (style == Style.BOLD) { + styleInt |= Font.BOLD; + } else if (style == Style.ITALIC) { + styleInt |= Font.ITALIC; + } + } + return styleInt; + } + + private static Style[] toVizStyles(int style) { + List