awips2/cave/com.raytheon.viz.gfe/localization/gfe/userPython/smartTools/SerpISC.py
2022-05-05 12:34:50 -05:00

485 lines
22 KiB
Python

##
# 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.
##
# ----------------------------------------------------------------------------
# SVN: $Revision$ - $Date$
# ----------------------------------------------------------------------------
# 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.
#
# serpISC - version 1.7
#
# Changes an existing grid to blend better into neighboring ISC grids.
# Can be used as an initial or final step in coordination. Only your grids
# are affected: nothing happens to the ISC grids. The ISC button must have
# been clicked on at least once before using this tool.
#
# Every point on the outer perimeter of CWA (i.e, belonging to selected ISCs)
# takes part in a serp adjustment of the existing grid. If any ISC grids are
# missing or not selected on a CWA boundary, your own grid is used there instead.
#
# You can use this tool on one ISC at a time to see how each one would influence
# your grid. To fit all ISC boundaries at once you must have all of them clicked
# on. Running the tool sequentially on each ISC will retain previous results if
# you keep the older ones turned on, but different sequences will yield slightly
# different results.
#
# Make sure your grid does not have an artificial boundary near the CWA border.
# Otherwise, it might already match your ISC neighbor there, so the tool won't
# adjust anything and your artificial boundary will remain.
#
# You can include or exclude as many sample points within your CWA as you like, but
# sample points close to an ISC border can create unrealistic gradients.
#
# You can match a border only partway if you want. Suppose you want to meet your
# ISC neighbor half way. Then set the "percent of full match" to 50. After sending
# your ISC grid, your neighbor will want to match FULL way (not half) to meet the
# newly received grid. You can also use "percent of full match" to nudge your
# grid to your neighbors' grids.
#
# If your grid's duration spans several shorter-duration ISC grids, the ISC
# grids will be time-averaged first (except for PoP which always uses the
# maximum value) and the fit will be inexact. Or, if the ISC grids themselves
# don't match at a CWA boundary (something you can't do in your own grid), the
# the tool will converge intermediate contours to the point of the mismatch,
# and the fit will look artificial.
#
# For winds serp runs twice, once for u and once for v.
#
# This tool cannot be used with Wx grids.
#
# Authors: Les Colin - WFO Boise, ID, and Tim Barker - SOO Boise, ID
#
# 2003/06/21 - Revised "remoteness" calculation (to counteract observation-
# clustering). New module is called getGoodRemoteness.
# numpy-Python code: Barker. Algorithm: Colin.
# 2003/06/22 - Analyzes winds in u and v components, rather than by speed
# and direction.
# 2003/06/23 - Finishes tool by copying ISC data outside CWA.
# 2003/10/29 - Runs serp without considering sample points, then runs it
# again only on the samples. ISC-copy feature has been removed.
# 2004/05/30 - Uses improved serp analysis (see Barker). Can include or exclude
# various ISC neighbors. Can include or exclude currently displayed
# samples within your CWA. Samples in the ISC areas are ignored.
# 2004/07/09 - Modified to ignore duplicate sample points (previously, they
# would hang the tool). Also modified tool to allow partial match
# so that CWA grid adjusts only partway toward ISC grid.
# 2004/09/04 - Modified to work on an edit area, perhaps only half way across the
# home CWA. The effect is a taper from a full (or partial) adjustment
# at designated ISC borders to zero change inside the home CWA where
# the edit area stops.
# 2004/09/21 - Now works even if preceded by ISC_Copy (by moving the home CWA-border
# inward one pixel and comparing to nearest ISC neighbor values).
# Tool completes by running an equivalent ISC_Copy on the selected ISC
# borders. Tool now also contains a thinning feature to speed up
# execution. e.g., thinning by 2 runs the tool on alternate border
# points, thinning by 3 runs the tool on every third border point, etc.
# 2004/09/25 - Corrected bug in preceding version in which sample points could possibly
# coincide with the revised home CWA-border points and hang the tool.
# 2004/11/10 - Final ISC_Copy feature made optional.
# 2004/11/17 - Corrected return statement at end of tool, and repaired code when
# NOT adjusting for elevation.
# 2008/07/31 - added int() for arguments to createTimeRange for OB8.3. /TB
# 2012/07/13 - Version 1.7. AWIPS2 Port.
# ----------------------------------------------------------------------------
# SOFTWARE HISTORY
#
# Date Ticket# Engineer Description
# ------------- -------- --------- --------------------------------------------
#
# Feb 15, 2018 7051 randerso Fixed mask data type issues
##
##
# This is an absolute override file, indicating that a higher priority version
# of the file will completely replace a lower priority version of the file.
##
ToolType = "numeric"
WeatherElementEdited = "variableElement"
ScreenList = ["SCALAR", "VECTOR"]
#
#====================================================================
# Part to modify for local configuration
defaultCWA = "STO"
VariableList = [
("Include these WFOs:", ["MTR", "EKA", "HNX", "REV", "MFR"], "check", ["MTR", "EKA", "HNX", "REV", "MFR"]),
("Intentional mismatch (CWA minus WFO):", "0", "alphaNumeric"),
("Currently displayed CWA sample points:", "Use", "radio", ["Use", "Don't use"]),
("Adjust for terrain elevation?", "Yes", "radio", ["Yes", "No"]),
("Elevation Factor", 36, "numeric"),
("Tool thinning-factor:", 1, "scale", [1, 10], 1),
("Percent of full match", 100, "scale", [0, 100], 1),
("Copy ISC data in afterward?", "No", "radio", ["Yes", "No"]),
]
from numpy import *
import ObjAnal
import SmartScript
import time
class Tool (SmartScript.SmartScript):
def __init__(self, dbss):
self._dbss = dbss
SmartScript.SmartScript.__init__(self, dbss)
def preProcessTool(self, varDict):
self.OA = ObjAnal.ObjAnal(self._dbss)
def execute(self, variableElement, variableElement_GridInfo, editArea, varDict, Topo, WEname, GridTimeRange):
wxType = variableElement_GridInfo.getGridType().ordinal()
defCWA = self.getEditArea(defaultCWA)
defcwa = self.encodeEditArea(defCWA)
nondefcwa = logical_not(defcwa) # i.e., toggle
nondefCWA = self.decodeEditArea(nondefcwa)
defea = self.taperGrid(nondefCWA, 2) * 2
# The above line defines the default CWA area as defea==0, the outer perimeter of the default CWA
# as defea==1, and further outside as defea==2.
arbea = self.encodeEditArea(editArea)
nonarbea = logical_not(arbea)
nonarbEA = self.decodeEditArea(nonarbea)
arbea = self.taperGrid(nonarbEA, 2) * 2
cwa = self.empty(bool)
ISC = varDict["Include these WFOs:"]
samps = varDict["Currently displayed CWA sample points:"]
thin = varDict["Tool thinning-factor:"]
partial = varDict["Percent of full match"] * .01
for WFO in ISC:
CWA = self.getEditArea(WFO)
cwa |= self.encodeEditArea(CWA)
alltrs = self._getAllHourlyTimeRanges(GridTimeRange)
if ((WEname == "MaxT")or(WEname == "PoP")):
sum = self.newGrid(-150.0)
elif (WEname == "MinT"):
sum = self.newGrid(150.0)
else:
if (wxType == 2):
sum = [self.empty(), self.empty()]
else:
sum = self.empty()
cnt = self.empty()
for tr in alltrs:
isc = self.getComposite(WEname, tr, 0)
if isc is None:
continue
#
# Add to sums, or min/max
#
if wxType == 1: # SCALAR
bits, iscgrid = isc
if ((WEname == "MaxT")or(WEname == "PoP")):
sum = where(bits, maximum(iscgrid, sum), sum)
cnt[bits] = 1
elif (WEname == "MinT"):
sum = where(bits, minimum(iscgrid, sum), sum)
cnt[bits] = 1
else:
sum = where(bits, sum + iscgrid, sum)
cnt[bits] += 1
if wxType == 2: # VECTOR
bits, mag, dir = isc
(u, v) = self.MagDirToUV(mag, dir)
sum[0] = where(bits, sum[0] + u, sum[0])
sum[1] = where(bits, sum[1] + v, sum[1])
cnt[bits] += 1
#
# now calculate average/max/min, etc.
# (count is always 1 for max/min)
#
if ((wxType == 1)or(wxType == 2)):
if (wxType == 2):
(mag, dir) = variableElement
(u, v) = self.MagDirToUV(mag, dir)
sum[0] = where(equal(cnt, 0), u, sum[0])
sum[1] = where(equal(cnt, 0), v, sum[1])
else:
sum = where(equal(cnt, 0), variableElement, sum)
cnt[equal(cnt, 0)] = 1
new = sum / cnt
if (wxType == 2):
(mag, dir) = self.UVToMagDir(new[0], new[1])
self.elevadjust = 0
self.elevfactor = 0.
if varDict["Adjust for terrain elevation?"] == "Yes":
self.elevadjust = 1
self.elevfactor = varDict["Elevation Factor"]
if self.elevfactor < 1:
self.elevfactor = 0.
self.xloclist = []
self.yloclist = []
self.hloclist = []
self.zlist = []
self.ulist = []
self.vlist = []
for x in range(1, Topo.shape[1] - 1):
for y in range(1, Topo.shape[0] - 1):
if (x + y) % thin != 0:
continue
if (arbea[y, x] < 2 and defea[y, x] == 0):
if (cwa[y, x + 1]) or (cwa[y, x - 1]) or (cwa[y + 1, x]) or (cwa[y - 1, x]):
if self.elevadjust == 1:
self.hloclist.append(Topo[y, x])
else:
self.hloclist.append(0.)
self.xloclist.append(x)
self.yloclist.append(y)
if wxType == 1:
chgval = 0.
n = 0
if cwa[y, x + 1] == 1:
if self.elevadjust == 0:
chgval = chgval + (new[y, x + 1] - variableElement[y, x])
elif self.elevadjust == 1:
elevdif = abs(Topo[y, x] - Topo[y, x + 1])
if elevdif < 5000.:
# ISC-CWA neighbors more than 5000 ft apart in elevation are too
# dissimilar to compare.
chgval = chgval + (new[y, x + 1] - variableElement[y, x]) * (1.0 - elevdif / 5000.)
n = n + 1
if cwa[y, x - 1] == 1:
if self.elevadjust == 0:
chgval = chgval + (new[y, x - 1] - variableElement[y, x])
elif self.elevadjust == 1:
elevdif = abs(Topo[y, x] - Topo[y, x - 1])
if elevdif < 5000.:
chgval = chgval + (new[y, x - 1] - variableElement[y, x]) * (1.0 - elevdif / 5000.)
n = n + 1
if cwa[y + 1, x] == 1:
if self.elevadjust == 0:
chgval = chgval + (new[y + 1, x] - variableElement[y, x])
elif self.elevadjust == 1:
elevdif = abs(Topo[y, x] - Topo[y + 1, x])
if elevdif < 5000.:
chgval = chgval + (new[y + 1, x] - variableElement[y, x]) * (1.0 - elevdif / 5000.)
n = n + 1
if cwa[y - 1, x] == 1:
if self.elevadjust == 0:
chgval = chgval + (new[y - 1, x] - variableElement[y, x])
elif self.elevadjust == 1:
elevdif = abs(Topo[y, x] - Topo[y - 1, x])
if elevdif < 5000.:
chgval = chgval + (new[y - 1, x] - variableElement[y, x]) * (1.0 - elevdif / 5000.)
n = n + 1
self.zlist.append((chgval / n) * partial)
elif wxType == 2:
(magcwa, dircwa) = variableElement
(ucwa, vcwa) = self.MagDirToUV(magcwa, dircwa)
(uisc, visc) = self.MagDirToUV(mag, dir)
chgu = 0.
chgv = 0.
n = 0
if cwa[y, x + 1] == 1:
if self.elevadjust == 0:
chgu = chgu + (uisc[y, x + 1] - ucwa[y, x])
chgv = chgv + (visc[y, x + 1] - vcwa[y, x])
elif self.elevadjust == 1:
elevdif = abs(Topo[y, x] - Topo[y, x + 1])
if elevdif < 5000.:
chgu = chgu + (uisc[y, x + 1] - ucwa[y, x]) * (1.0 - elevdif / 5000.)
chgv = chgv + (visc[y, x + 1] - vcwa[y, x]) * (1.0 - elevdif / 5000.)
n = n + 1
if cwa[y, x - 1] == 1:
if self.elevadjust == 0:
chgu = chgu + (uisc[y, x - 1] - ucwa[y, x])
chgv = chgv + (visc[y, x - 1] - vcwa[y, x])
elif self.elevadjust == 1:
elevdif = abs(Topo[y, x] - Topo[y, x - 1])
if elevdif < 5000.:
chgu = chgu + (uisc[y, x - 1] - ucwa[y, x]) * (1.0 - elevdif / 5000.)
chgv = chgv + (visc[y, x - 1] - vcwa[y, x]) * (1.0 - elevdif / 5000.)
n = n + 1
if cwa[y + 1, x] == 1:
if self.elevadjust == 0:
chgu = chgu + (uisc[y + 1, x] - ucwa[y, x])
chgv = chgv + (visc[y + 1, x] - vcwa[y, x])
elif self.elevadjust == 1:
elevdif = abs(Topo[y, x] - Topo[y + 1, x])
if elevdif < 5000.:
chgu = chgu + (uisc[y + 1, x] - ucwa[y, x]) * (1.0 - elevdif / 5000.)
chgv = chgv + (visc[y + 1, x] - vcwa[y, x]) * (1.0 - elevdif / 5000.)
n = n + 1
if cwa[y - 1, x] == 1:
if self.elevadjust == 0:
chgu = chgu + (uisc[y - 1, x] - ucwa[y, x])
chgv = chgv + (visc[y - 1, x] - vcwa[y, x])
elif self.elevadjust == 1:
elevdif = abs(Topo[y, x] - Topo[y - 1, x])
if elevdif < 5000.:
chgu = chgu + (uisc[y - 1, x] - ucwa[y, x]) * (1.0 - elevdif / 5000.)
chgv = chgv + (visc[y - 1, x] - vcwa[y, x]) * (1.0 - elevdif / 5000.)
n = n + 1
self.ulist.append((chgu / n) * partial)
self.vlist.append((chgv / n) * partial)
if arbea[y, x] == 1 and defea[y, x] == 0:
self.pointok = 0
for nn in range(len(self.xloclist)):
if (y == self.yloclist[nn]) and (x == self.xloclist[nn]):
self.pointok = 1
# In the above line an edit area IS on the screen and here we're looking for boundary points
# inside the home CWA that are more than one pixel from the border. We want to hold these
# points steady (i.e., zero change).
if self.pointok == 1: # we already have this point, don't use it twice.
continue
self.xloclist.append(x)
self.yloclist.append(y)
if self.elevadjust == 1:
self.hloclist.append(Topo[y, x])
else:
self.hloclist.append(0.)
if wxType == 1:
self.zlist.append(0.)
if wxType == 2:
self.ulist.append(0.)
self.vlist.append(0.)
if samps == "Use":
self.samplePoints = self.getSamplePoints(None)
for sample in self.samplePoints:
(x, y) = sample
self.sampleok = 0
for count in range(len(self.xloclist)):
if ((x == self.xloclist[count]) and (y == self.yloclist[count])):
self.sampleok = 1
# self.sampleok becomes 1 for a duplicate entry, so bypass the duplicate.
if self.sampleok == 1:
continue
if x < 0 or x > Topo.shape[1] - 1:
continue
if y < 0 or y > Topo.shape[0] - 1:
continue
if defea[y, x] != 0:
continue
if self.elevadjust == 1:
self.hloclist.append(Topo[y, x])
else:
self.hloclist.append(0.)
self.xloclist.append(x)
self.yloclist.append(y)
if wxType == 1:
self.zlist.append(0.)
if wxType == 2:
self.ulist.append(0.)
self.vlist.append(0.)
#
# Don't proceed if no points
#
if len(self.xloclist) == 0:
self.statusBarMsg("No data available to serp to...", "R")
return variableElement
else:
print(" the number of points being used:", len(self.xloclist))
#
#
#
if wxType == 1: # scalar
zval = self.OA.Serp(self.zlist, self.xloclist, self.yloclist, self.hloclist, self.elevfactor, Topo)
# zval is the new scalar-change grid.
if varDict["Copy ISC data in afterward?"] == "Yes":
znew = where(logical_or(equal(defea, 0), equal(cwa, 0)), variableElement + zval, new)
else:
znew = variableElement + zval
if wxType == 2: # vector
zval = self.OA.Serp(self.ulist, self.xloclist, self.yloclist, self.hloclist, self.elevfactor, Topo)
# zval is the new u-change grid.
if varDict["Copy ISC data in afterward?"] == "Yes":
newu = where(logical_or(equal(defea, 0), equal(cwa, 0)), ucwa + zval, new[0])
else:
newu = ucwa + zval
zval = self.OA.Serp(self.vlist, self.xloclist, self.yloclist, self.hloclist, self.elevfactor, Topo)
# this zval is the new v-change grid.
if varDict["Copy ISC data in afterward?"] == "Yes":
newv = where(logical_or(equal(defea, 0), equal(cwa, 0)), vcwa + zval, new[1])
else:
newv = vcwa + zval
(newspd, newdir) = self.UVToMagDir(newu, newv)
# newspd=where(equal(defea+cwa,0),newspd,mag)
# newdir=where(equal(defea+cwa,0),newdir,dir)
znew = (newspd, newdir)
absmax = variableElement_GridInfo.getMaxValue()
absmin = variableElement_GridInfo.getMinValue()
if wxType == 1:
return clip(znew, absmin, absmax)
else:
return znew
#===================================================================
# _getAllHourlyTimeRanges - gets a list of all 1-hour time ranges
# within the specified time range
#
def _getAllHourlyTimeRanges(self, tr):
#
# get integer time of UTC midnight today
#
secsinhour = 60 * 60
lt = time.gmtime()
mid = time.mktime((lt[0], lt[1], lt[2], 0, 0, 0, lt[6], lt[7], lt[8]))
#
# get integer time of input timerange start
#
start = tr.startTime()
year = start.year
month = start.month
day = start.day
hour = start.hour
trs = time.mktime((year, month, day, hour, 0, 0, lt[6], lt[7], lt[8]))
#
# get integer time of input timerange end
#
end = tr.endTime()
year = end.year
month = end.month
day = end.day
hour = end.hour
tre = time.mktime((year, month, day, hour, 0, 0, lt[6], lt[7], lt[8]))
#
# The difference between start/end determines number of hours
#
numhours = int((tre - trs) / secsinhour)
#
# Difference between mid/start determines starting offset
#
offset = int((trs - mid) / secsinhour)
#
# create each hourly time range from offset
#
alltrs = []
for hour in range(0, numhours):
newtr = self.createTimeRange(int(offset + hour), int(offset + hour + 1), "Zulu")
alltrs.append(newtr)
return alltrs