FAQ's

    Using a Different Area for Some Phrases
    Range Adjustment Questions/Answers
Using Lat/Lon Edit Areas in a Product


USING A DIFFERENT EDIT AREA FOR SOME PHRASES

Question: For the ZFP, I want to use a smaller edit area in each zone for temperature to capture the populated areas, but use the entire edit area for all other elements. I heard it can be done with "intersectAreas" but all references that I've found in the Text Formatter Reference Guide tie intersectAreas with local effects. I want to make it clear, I don't want local effects. I just want the text to say, "HIGHS IN THE MID 50S" and not "HIGHS IN THE MID 40S EXCEPT MID 50S IN THE POPULATED AREAS"

Answer:
There are several ways to accomplish this -- you can use "intersectAreas" or  "additionalAreas".  Perhaps the simplest is using "intersectAreas".

1) Define an edit area, say PopulatedAreas, that is the union of all your populated smaller areas.
2) Make this an "intersectArea" for MaxT and MinT in the product component definition.
3) Override the "highs_setUp" and "lows_setUp" methods from ScalarPhrases with the following additional code:
I would like to set up a varDict to allow the user to enter in a given latitude and longitude and have a given product run off that lat/lon pair as the default edit area. 

    def highs_setUp(self, tree, node):

        # This code re-sets the node's areaLabel to that for the
        # intersection of the current area and the smaller populated areas
        # After this is set, the phrase will work with the smaller area
        areaLabel = node.getAreaLabel()
        intersectName = self.getIntersectName(areaLabel, "PopulatedAreas")
        node.set("areaLabel", intersectName)

        elementInfoList = [self.ElementInfo("MaxT", "List")]
        self.subPhraseSetUp(tree, node, elementInfoList, self.scalarConnector)
        return self.DONE()

RANGE ADJUSTMENT QUESTIONS/ANSWERS

The following is a condensation of a listserver discussion involving Range Adjustment:

Kyle: In our grids for today, we have no wind speeds greater than 13 knots, but our formatters went with 15 to 25 mph during the afternoon. Same wording problem for the evening with the same wind speeds. Is there something in the set-up that I have incorrect?  The Overrides file is attached.

Tracy: The range for your winds is set to have a minimum of 10. I suspect this is why you are getting the 15-25 mph range.

    def minimum_range_nlValue_dict(self, tree, node):
        # This threshold is the "smallest" min/max difference allowed between values reported.
        # For example, if threshold is set to 5 for "MaxT", and the min value is 45
        # and the max value is 46, the range will be adjusted to at least a 5 degree
I would like to set up a varDict to allow the user to enter in a given latitude and longitude and have a given product run off that lat/lon pair as the default edit area. 
        # range e.g. 43-48.  These are the values that are then submitted for phrasing
        # such as:
        #   HIGHS IN THE MID 40S
        dict = TextRules.TextRules.minimum_range_nlValue_dict(self, tree, node)
        dict["MaxT"] = 1 #defualt 5
        dict["MinT"] = 1 #default 5
        dict["Wind"] = {
            (0,5):0,
            (5,15):5,
            (15,40):10,
            "default":15,
            }
        return dict

Kyle:   I changed it to 5 and it mostly worked. Instead of 15 to 25 it gave me 15 to 20. The only problem is we do not have a single grid over 15 mph...so it is kind of ignoring the fact that we have some winds less than 15 mph...and no winds above 15 mph.
Does it just take the highest wind at any one time, and use that as the lower end of the range? That's what it seems like it is doing, at least today.
This may be easier said than done, but would it be better to basically go +- 5 mph from the average wind? That way, if you average wind is 15 mph, you would be given 10-20. Maybe like this...

0 to 3 mph - light or variable or calm
4 to 7 mph could be around 5
8 to 12 = 5 to 15 or 10 to 15
13 to 17 = 10 to 20
18 to 23 = 15 to 25
24 to 27 = 20 to 30
28 to 33 = 25 to 35
34 to 37 = 30 to 40
38+ could have a range of 15, like 35 to 50.

Dave: Ahhhhhhh, we are now seeing the "Catch 22" when forcing ranges(wind, temperature, etc). When we try to stick with the standard(oldtime) NWS "ranges", you will end up with text forecasts that don't match what is in the grids.

Jay: In your local file, try changing the Wind entry in the maximum_range_bias_nlValue_dict from "Min" to "Max". I think you'll want to do the same thing for the minimum_range_bias_nlValue_dict, as well. I think this will solve your problem and give you wind text of 10 to 15 mph instead of 15 to 20 mph.

Dave: Once again, it is about rounding and ranges in the BASE level infrastructure. If you truly want accuracy, then there first should be no rounding and return literal values(i.e. 13 mph, 11 mph, etc). So, if you have 13kts, then you will get 15 mph. Add a 5 mph range, and you will get 15 to 20 mph. The infrastructure is nearly wide open, so you can change what you like.

Kyle: Thanks Jay, Was wondering, by doing this what, what would happen if my average wind was 17 mph? Would it round to 15 mph, then go 10 to 15 mph? I'm mainly curious about the logic of how these winds are calculated for each time block to see if there is a problem.

Wade: Here in PDT, we have primarily eliminated this problem by using the overrides below. As Dave mentioned, this is mainly a problem due to rounding. It's not only the round of the internal grid values, but it's also the rounding of ranges too, especially if you use Average. For example, let's say I have defined wind values between 5 and 15 mph as having a 5 mph range as a minimum and maximum range. Let's say all my values within a zone are 10 mph. Since 5 mph can't be divided evenly, the resulting range ends up being 10-15 mph (i.e., 10 minus 2.5 rounds to 10, and 10 plus 2.5 rounds to 15). This is probably why you are getting maximum values that don't exist in your grids. An excellent way to avoid this particular rounding problem is to use only even values in the maximum and minumum range value dictionaries.

          def maximum_range_nlValue_dict(self, tree, node):
# Maximum range to be reported within a phrase
I would like to set up a varDict to allow the user to enter in a given latitude and longitude and have a given product run off that lat/lon pair as the default edit area. 
# e.g. 5 to 10 mph
# Units depend on the product
dict = TextRules.TextRules.maximum_range_nlValue_dict(self, tree, node)
dict["MaxT"] = 15
dict["MinT"] = 15
dict["WindChill"] = 15
dict["HeatIndex"] = 15
dict["Wind"] = {
(0, 5) : 0,
(5, 100) : 10,
'default': 10,
}
return dict

def minimum_range_nlValue_dict(self, tree, node):
# This threshold is the "smallest" min/max difference allowed between values reported.
# For example, if threshold is set to 5 for "MaxT", and the min value is 45
# and the max value is 46, the range will be adjusted to at least a 5 degree
# range e.g. 43-48. These are the values that are then submitted for phrasing
# such as:
# HIGHS IN THE MID 40S
dict = TextRules.TextRules.minimum_range_nlValue_dict(self, tree, node)
dict["MaxT"] = 5
dict["MinT"] = 5
dict["WindChill"] = 10
dict["HeatIndex"] = 10
dict["Wind"] = {
(0, 5) : 0,
(5,100) : 10,
'default': 10,
}
return dict

def maximum_range_bias_nlValue_dict(self, tree, node):
# "Min", "Average", "Max"
# Should the maximum_range be taken from the "min" "average" or "max"
# value of the current range?
return {
# changed Max to Average
"MaxT": "Min",
"MinT": "Min",
"Wind": "Average",
"otherwise": "Average",
}

def minimum_range_bias_nlValue_dict(self, tree, node):
# "Min", "Average", "Max"
# Should the minimum_range be taken from the "min" "average" or "max"
# value of the current range?
return {
# changed Max to Average
"MaxT": "Min",
"MinT": "Min",
"Wind": "Average",
"otherwise": "Average",
}


Jay:  Every time I deal with this, my head spins. There's a lot going on behind the scenes with the rounding and the ranges.

It's been my experience that the vast majority of unexpected words comes when values are rounded to some value other than the unit value, e.g., 5 mph instead of 1 mph. The other key points are that rounding and ranges are applied essentially any time the data are needed. This means there's a lot of rounding and range application happening in the background.

There is a hierarchy of methods for determining ranges. Each range type as two configuration methods: an nlValue_dict and a bias_nlValue_dict. In the order they are applied, these are: range_nlValue_dict and range_bias_nlValue_dict minimum_range_nlValue_dict and minimum_range_bias_nlValue_dict maximum_range_nlValue_dict and maximum_range_bias_nlValue_dict

The range logic is used to force single value phrasing with the word "around". For example, if you set "Wind" to 5 in the range_nlValue_dict, then any wind range in the grids of less than 5 mph will generate a phrase like "AROUND 10 MPH". The bias value determines what part of the range is used for the value. If the range logic "fires", then neither the minimum_range nor the maximum_range logic "fires".

If the range logic does not "fire", then both the minimum_range and maximum range logic, in that order, runs. The minimum range ensures the range is a least the value in the nlValue_dict while the maximum range ensures the range is no more than the value in the nlValue_dict. The bias_nlValue_dict entries set which part of the existing range is used to create the new range. It's worth re-emphasizing that the minumum range and maximum range logic only applies if the range logic does not "fire". The only way to always guarantee a minimum range and a maximum range is to set the range_nlValue_dict entry to a value that will never trigger the range logic.

Let's use the winds you listed below and walk through the logic. The minimum wind is 12 and the maximum wind is 15. Before the range logic is applied, the values are rounded. So, this gives a range of 10 to 15 mph. I think the range_nlValue_dict entry for "Wind" defaults to 0. Since our range is not less than 0 mph, the range logic does not "fire".

The minimum range logic runs. Since the minimum range for "Wind" is 5 mph, nothing further happens because the existing range is not less
than 5 mph.

The maximum range logic runs. Since the maximum range is 10 mph, nothing further happens because the existing range is not more that 10 mph.

For the sake of argument, let's say you have a wind range from the grids of 12 to 28 mph. Rounding is applied and the range becomes 10 to 30 mph. This is more than the range_nlValue_dict threshold, so the range logic does not "fire", allowing the minimum and maximum range logic to run. The range is also more the the mimimum_range_nlValue_dict threshold, so the minimum range logic does not "fire". At this point, I have to admit I don't know which entry in the maximum_range_nlValue_dict applies in this case, which is why I made the range 20. Let's say it's 15 mph. Anyway, the existing range exceeds the maximum range. Now, here's where the bias comes into play. If the bias were "Min", the new range would be 10 to 25 mph. The minimum of the existing range becomes the minimum of the new range and the desired spread is added to get the new max. If the bias were "Max", the new range would be 15 to 30 mph. The max of the existing range becomes the max of the new range and the desired spread is subtracted to get the new min. Note: the desired range value was rounded before being used to create the new range. Since it was already a multiple of 5, nothing unexpected happened. If the bias were "Average", the new range becomes 15 to 30 mph. Very interesting. The average of the existing range becomes the center point of the new range. Then half the desired range value is subtracted from/added to that average to get the new min and max (12.5 and 27.5). Finally, the new min and max are rounded. In this case, the rounding gave us values we might not have expected.

Your question of what would happen if the average of the wind range was 17 mph has no meaning unless the corresponding bias is "Average".

My guess for what happened to you originally is as follows. The minimum and maximum values passed to the formatter were in the range 12.5 mph <= Wind < 17.5 mph. Keep in mind that what the GFE displays for a value may not be exactly what the value is in the database. Plus, as data come into the formatters from the grids, they are both converted and rounded. I think your formatter got a wind range tuple of (15, 15). With the range_bias value 0, the range logic doesn't fire because 0 is not less than 0. Then we get to the minimum range logic. You originally had the value as 10 mph and the bias as minimum. The existing mimimum was 15, add 10 to get 25, and you end up with text of 15 to 25 mph, even though that's not in the grids. However, the formatter did exactly what it was told to do, even if it wasn't what you expected/wanted.

If you want to look at the range code yourself, look at the applyRangeValues and applyBias methods in TextUtils.

Using Lat/Lon Edit Areas in a Product

Question: I would like to set up a varDict to allow the user to enter in a given latitude and longitude and have a given product run off that lat/lon pair as the default edit area.

Answer:
1. Set up the VariableList so the suer can enter lat/lon values.  For
example:

VariableList = [
(("forecast point latitude", "lat"), 20.0, "alphaNumeric"),
(("forecast point longitude", "lon"), -160.0, "alphaNumeric"),
]

2. You can set the Definition["defaultEditAreas"] = [] since it will not
be used at all.

3. In your formatter "generateForecast" method, replace this:

self._areaList = self.getAreaList(argDict)

with something this:

self._areaList = self._getLatLonAreaList(argDict)

4. Finally, include the following method:

def _getLatLonAreaList(self, argDict):

area = self.createLatLonArea(float(self._lat), float(self._lon), 10)

# OPTIONAL SET UP IF YOU WANT THIS AREA SAMPLED FOR HAZARDS
# Save to server
self.saveEditAreas([area])
# Create Hazards Table for this area
hazards = HazardsTable.HazardsTable(
argDict["ifpClient"], [[area.id().name()]], "FWS",
self.filterMethod, argDict["databaseID"],
self._fullStationID)
argDict["hazards"] = hazards
# Remove from server
self.deleteEditAreas([area])

return [(area, "areaName")]