Merge branch 'CLIMATE-942'
diff --git a/RCMES/CORDEX/cordex.py b/RCMES/CORDEX/cordex.py
index dc80be4..4b4a4e8 100644
--- a/RCMES/CORDEX/cordex.py
+++ b/RCMES/CORDEX/cordex.py
@@ -1,56 +1,62 @@
-import os
-import subprocess
-import jinja2
-from metadata_extractor import CORDEXMetadataExtractor, obs4MIPSMetadataExtractor
-
-# These should be modified. TODO: domains can also be made into separate group
-# CORDEX domain
-domain = 'NAM-44'
-
-# The output directory
-workdir = '/home/goodman/data_processing/CORDEX/analysis'
-
-# Location of osb4Mips files
-obs_dir = '/proj3/data/obs4mips'
-
-# Location of CORDEX files
-models_dir = '/proj3/data/CORDEX/{domain}/*'.format(domain=domain)
-
-# Extract metadata from model and obs files, pairing up files with the same
-# variables for separate evaluations
-obs_extractor = obs4MIPSMetadataExtractor(obs_dir)
-models_extractor = CORDEXMetadataExtractor(models_dir)
-groups = obs_extractor.group(models_extractor, 'variable')
-
-# Configuration file template, to be rendered repeatedly for each evaluation
-# run
-env = jinja2.Environment(loader=jinja2.FileSystemLoader('./templates'),
- trim_blocks=True, lstrip_blocks=True)
-t = env.get_template('CORDEX.yaml.template')
-
-# Each group represents a single evaluation. Repeat the evaluation for
-# three seasons: Summer, Winter, and Annual.
-seasons = ['annual', 'winter', 'summer']
-for group in groups:
- obs_info, models_info = group
- instrument = obs_info['instrument']
- variable = obs_info['variable']
- for season in seasons:
- configfile_basename = '_'.join([domain, instrument, variable, season]) + '.yaml'
+import os
+import sys
+import subprocess
+import jinja2
+from metadata_extractor import CORDEXMetadataExtractor, obs4MIPSMetadataExtractor
+
+# These should be modified. TODO: domains can also be made into separate group
+# CORDEX domain
+
+user_input = sys.argv[1:]
+if len(user_input) == 4:
+ domain, workdir, obs_dir, models_dir = user_input[:]
+else:
+ domain = 'NAM-44'
+
+ # The output directory
+ workdir = os.getcwd()+'/'+domain+'_analysis'
+
+ # Location of osb4Mips files
+ obs_dir = '/proj3/data/obs4mips'
+
+ # Location of CORDEX files
+ models_dir = '/proj3/data/CORDEX/{domain}/*'.format(domain=domain)
+
+# Extract metadata from model and obs files, pairing up files with the same
+# variables for separate evaluations
+obs_extractor = obs4MIPSMetadataExtractor(obs_dir)
+models_extractor = CORDEXMetadataExtractor(models_dir)
+groups = obs_extractor.group(models_extractor, 'variable')
+
+# Configuration file template, to be rendered repeatedly for each evaluation
+# run
+env = jinja2.Environment(loader=jinja2.FileSystemLoader('./templates'),
+ trim_blocks=True, lstrip_blocks=True)
+t = env.get_template('CORDEX.yaml.template')
+
+# Each group represents a single evaluation. Repeat the evaluation for
+# three seasons: Summer, Winter, and Annual.
+seasons = ['annual', 'winter', 'summer']
+errored = []
+for group in groups:
+ obs_info, models_info = group
+ instrument = obs_info['instrument']
+ variable = obs_info['variable']
+ for season in seasons:
+ configfile_basename = '_'.join([domain, instrument, variable, season]) + '.yaml'
configfile_path = os.path.join(workdir, domain, instrument,
- variable, season)
- if not os.path.exists(configfile_path):
- os.makedirs(configfile_path)
- configfile_path = os.path.join(configfile_path, configfile_basename)
- with open(configfile_path, 'w') as configfile:
- configfile.write(t.render(obs_info=obs_info, models_info=models_info,
- season=season, output_dir=workdir))
-
- # TODO: Do this in parallel. Will change this once this approach
- # is well tested.
- code = subprocess.call(['python', '../run_RCMES.py', configfile_path])
- errored = []
- if code:
- errored.append(configfile_path)
-
-print("All runs done. The following ended with an error: {}".format(errored))
+ variable, season)
+ if not os.path.exists(configfile_path):
+ os.makedirs(configfile_path)
+ configfile_path = os.path.join(configfile_path, configfile_basename)
+ with open(configfile_path, 'w') as configfile:
+ configfile.write(t.render(obs_info=obs_info, models_info=models_info,
+ season=season, output_dir=workdir))
+
+ # TODO: Do this in parallel. Will change this once this approach
+ # is well tested.
+ code = subprocess.call([sys.executable, '../run_RCMES.py', configfile_path])
+ if code:
+ errored.append(configfile_path)
+
+print("All runs done. The following ended with an error: {}".format(errored))
diff --git a/ocw-ui/frontend/.jscsrc b/ocw-ui/frontend/.jscsrc
new file mode 100644
index 0000000..cb61269
--- /dev/null
+++ b/ocw-ui/frontend/.jscsrc
@@ -0,0 +1,17 @@
+{
+ "disallowTrailingWhitespace": true,
+ "disallowUnusedParams": true,
+ "disallowUnusedVariables": true,
+ "requireCapitalizedComments": true,
+ "requireCurlyBraces": true,
+ "requireDotNotation": true,
+ "requireLineFeedAtFileEnd": true,
+ "requireSemicolons": true,
+ "requireSpaceAfterComma": true,
+ "requireSpaceAfterLineComment": true,
+ "requireSpaceBetweenArguments": true,
+ "requireSpacesInConditionalExpression": true,
+ "requireTemplateStrings": true,
+ "requireYodaConditions": true,
+ "validateQuoteMarks": "'"
+}
diff --git a/ocw-ui/frontend/.jshintrc b/ocw-ui/frontend/.jshintrc
index 40377ba..ea35f00 100644
--- a/ocw-ui/frontend/.jshintrc
+++ b/ocw-ui/frontend/.jshintrc
@@ -18,7 +18,9 @@
"strict": true,
"trailing": true,
"smarttabs": true,
+ "varstmt": true,
"globals": {
- "angular": false
+ "angular": false,
+ "$": false
}
}
diff --git a/ocw-ui/frontend/Gruntfile.js b/ocw-ui/frontend/Gruntfile.js
index a58f762..43757c8 100644
--- a/ocw-ui/frontend/Gruntfile.js
+++ b/ocw-ui/frontend/Gruntfile.js
@@ -132,6 +132,14 @@
}
},
+ // Supplements jshint with a focus on style.
+ jscs: {
+ src: '<%= yeoman.app %>/scripts/{,*/}*.js',
+ options: {
+ config: ".jscsrc",
+ fix: false
+ }
+ },
// Empties folders to start fresh
clean: {
dist: {
@@ -408,6 +416,7 @@
grunt.registerTask('default', [
'newer:jshint',
+ 'jscs',
'test',
'build'
]);
diff --git a/ocw-ui/frontend/app/scripts/controllers/parameterselect.js b/ocw-ui/frontend/app/scripts/controllers/parameterselect.js
index 12bd65b..9b52e53 100644
--- a/ocw-ui/frontend/app/scripts/controllers/parameterselect.js
+++ b/ocw-ui/frontend/app/scripts/controllers/parameterselect.js
@@ -79,6 +79,20 @@
return res;
}
+ let formatDate = function(oldDate) {
+
+ let newDate = new Date(oldDate);
+
+ let year = newDate.getFullYear();
+
+ let month = ((newDate.getMonth() + 1) < 10) ? '0' + (newDate.getMonth() + 1).toString: (newDate.getMonth() + 1);
+
+ let day = (newDate.getDate() < 10) ? '0' + newDate.getDate().toString: newDate.getDate();
+
+ return newDate.getFullYear() + '-' + (newDate.getMonth() + 1) + '-' + newDate.getDate();
+ }
+
+
$scope.runEvaluation = function() {
$scope.runningEval = true;
@@ -160,8 +174,8 @@
}
// Set the bound values for the evaluation
- data['start_time'] = $scope.displayParams.start + " 00:00:00",
- data['end_time'] = $scope.displayParams.end + " 00:00:00",
+ data['start_time'] = formatDate($scope.displayParams.start) + " 00:00:00",
+ data['end_time'] = formatDate($scope.displayParams.end) + " 00:00:00",
data['lat_min'] = $scope.displayParams.latMin,
data['lat_max'] = $scope.displayParams.latMax,
data['lon_min'] = $scope.displayParams.lonMin,
@@ -234,8 +248,8 @@
latMax = (curDataset['latlonVals']['latMax'] < latMax) ? curDataset['latlonVals']['latMax'] : latMax;
lonMin = (curDataset['latlonVals']['lonMin'] > lonMin) ? curDataset['latlonVals']['lonMin'] : lonMin;
lonMax = (curDataset['latlonVals']['lonMax'] < lonMax) ? curDataset['latlonVals']['lonMax'] : lonMax;
- start = (curDataset['timeVals']['start'] > start) ? curDataset['timeVals']['start'] : start;
- end = (curDataset['timeVals']['end'] < end) ? curDataset['timeVals']['end'] : end;
+ start = (curDataset['timeVals']['start'] > start) ? new Date(curDataset['timeVals']['start']) : start;
+ end = (curDataset['timeVals']['end'] < end) ? new Date(curDataset['timeVals']['end']) : end;
datasetRegrid = datasetRegrid || curDataset.regrid;
@@ -249,16 +263,16 @@
$scope.displayParams.latMax = $scope.truncateFloat(latMax);
$scope.displayParams.lonMin = $scope.truncateFloat(lonMin);
$scope.displayParams.lonMax = $scope.truncateFloat(lonMax);
- $scope.displayParams.start = (typeof start == 'undefined') ? "" : start.split(" ")[0];
- $scope.displayParams.end = (typeof end == 'undefined') ? "" : end.split(" ")[0];
+ $scope.displayParams.start = (typeof start == 'undefined') ? "" : new Date(start);
+ $scope.displayParams.end = (typeof end == 'undefined') ? "" : new Date(end);
// Update the local store values!
$scope.latMin = latMin;
$scope.latMax = latMax;
$scope.lonMin = lonMin;
$scope.lonMax = lonMax;
- $scope.start = (typeof start == 'undefined') ? "" : start.split(" ")[0];
- $scope.end = (typeof end == 'undefined') ? "" : end.split(" ")[0];
+ $scope.start = (typeof start == 'undefined') ? "" : start;
+ $scope.end = (typeof end == 'undefined') ? "" : end;
$scope.displayParams.areValid = true;
$rootScope.$broadcast('redrawOverlays', []);
diff --git a/ocw-ui/frontend/app/scripts/controllers/rcmedselection.js b/ocw-ui/frontend/app/scripts/controllers/rcmedselection.js
index 81d7d21..484cb7b 100644
--- a/ocw-ui/frontend/app/scripts/controllers/rcmedselection.js
+++ b/ocw-ui/frontend/app/scripts/controllers/rcmedselection.js
@@ -81,16 +81,20 @@
};
$scope.dataSelectUpdated = function() {
- var urlString = $rootScope.baseURL + '/rcmed/parameters/dataset/' +
- $scope.datasetSelection["shortname"] +
- "?callback=JSON_CALLBACK";
- $http.jsonp(urlString)
- .success(function(data) {
- $scope.retrievedObsParams = data;
- if ($scope.retrievedObsParams.length > 1)
- $scope.retrievedObsParams.splice(0, 0, {shortname: 'Please select a parameter'});
- $scope.parameterSelection = $scope.retrievedObsParams[0];
- });
+ if ($scope.datasetSelection) {
+ var urlString = $rootScope.baseURL + '/rcmed/parameters/dataset/' +
+ $scope.datasetSelection["shortname"] +
+ "?callback=JSON_CALLBACK";
+
+ $http.jsonp(urlString)
+ .success(function(data) {
+ $scope.retrievedObsParams = data;
+ if ($scope.retrievedObsParams.length > 1)
+ $scope.retrievedObsParams.splice(0, 0, {longname: 'Please select a parameter'});
+ $scope.parameterSelection = $scope.retrievedObsParams[0];
+ }
+ );
+ }
};
$scope.addObservation = function() {
diff --git a/ocw-ui/frontend/app/scripts/controllers/timeline.js b/ocw-ui/frontend/app/scripts/controllers/timeline.js
index 2546ede..8d734b6 100644
--- a/ocw-ui/frontend/app/scripts/controllers/timeline.js
+++ b/ocw-ui/frontend/app/scripts/controllers/timeline.js
@@ -41,7 +41,7 @@
// Don't process if no datasets have been added
if ($scope.datasets.length == 0 || !("timeline" in $rootScope))
return;
-
+
// Create DataTable to add data to timeline
var data = new google.visualization.DataTable();
data.addColumn('datetime', 'start');
@@ -66,7 +66,7 @@
new Date(end.substr(0, 4), end.substr(5, 2) - 1, end.substr(8, 2)));
// Add user selected bounds to timeline
- if ($scope.regionParams.areValid) {
+ if ($scope.regionParams.start && $scope.regionParams.end) {
var userStart = $scope.regionParams.start;
var userEnd = $scope.regionParams.end;
@@ -74,22 +74,22 @@
// Add color to user selected bounds
var style = 'background-color: #000000; border: 2px solid;';
var ocwBar = '<div class="ocw-bar timeline-event-range" style="' + style + '"></div>';
-
+
// Add row to DataTable: object with start and end date
// note: subtract one from month since indexes from 0 to 11
- data.addRow([new Date(userStart.substr(0,4), userStart.substr(5,2)-1, userStart.substr(8,2)),
- new Date(userEnd.substr(0,4), userEnd.substr(5,2)-1, userEnd.substr(8,2)),
+ data.addRow([new Date(userStart),
+ new Date(userEnd),
ocwBar ]);
}
-
+
var options = {
"width": "100%",
"showCurrentTime": false,
"moveable": false,
"zoomable": false,
};
-
- // Draw timeline with data (DataTable) and options (a name-value map)
+
+ // Draw timeline with data (DataTable) and options (a name-value map)
$rootScope.timeline.draw(data, options);
};
diff --git a/ocw-ui/frontend/app/scripts/controllers/worldmap.js b/ocw-ui/frontend/app/scripts/controllers/worldmap.js
index 0d28d6c..e602ee7 100644
--- a/ocw-ui/frontend/app/scripts/controllers/worldmap.js
+++ b/ocw-ui/frontend/app/scripts/controllers/worldmap.js
@@ -41,7 +41,7 @@
// Don't process if we don't have any datasets added or if the map doesn't exist!!
if ($scope.datasets.length == 0 || !("map" in $rootScope))
return;
-
+
// Create a group that we'll draw overlays to
$rootScope.rectangleGroup = L.layerGroup();
// Add rectangle Group to map
@@ -78,7 +78,7 @@
$rootScope.rectangleGroup.addLayer(overlapBorder);
// Draw user selected region
- if ($scope.regionParams.areValid) {
+ if (!isNaN($scope.regionParams.lonMin) && !isNaN($scope.regionParams.lonMax) && !isNaN($scope.regionParams.latMin) && !isNaN($scope.regionParams.latMax)) {
var bounds = [[$scope.regionParams.latMax, $scope.regionParams.lonMin],
[$scope.regionParams.latMin, $scope.regionParams.lonMax]];
diff --git a/ocw-ui/frontend/app/views/main.html b/ocw-ui/frontend/app/views/main.html
index f07875f..3e94ba5 100644
--- a/ocw-ui/frontend/app/views/main.html
+++ b/ocw-ui/frontend/app/views/main.html
@@ -113,10 +113,10 @@
<div class="datasetNameDisplay col-md-8 col-md-offset-1 muted" tooltip="{{dataset.name}}">
{{dataset.name}}
</div>
- <div class="col-md-1 col-md-offset-2">
- <span tooltip-placement="left" tooltip-popup-delay="700" tooltip="Remove Dataset">
- <a class="no-color-link" href="#" ng-click="removeDataset($index)">
- <i class="fa fa-remove"></i>
+ <div class="col-md-2 col-md-offset-2">
+ <span tooltip-placement="left" tooltip-popup-delay="700" tooltip="Remove Dataset">
+ <a class="no-color-link pull-right" href="#" ng-click="removeDataset($index)">
+ <i class="fa fa-times"/>
</a>
</span>
</div>
@@ -156,8 +156,8 @@
<!--Preview Map Section-->
<div class="col-md-3">
<!--If the dataset is global we show a picture of a globe instead of the actual map-->
- <div ng-hide="dataset.latlonVals.lonMin == -180 && dataset.latlonVals.lonMax == 180 &&
- dataset.latlonVals.latMin == -90 && dataset.latlonVals.latMax == 90"
+ <div ng-hide="dataset.latlonVals.lonMin == -180 && dataset.latlonVals.lonMax == 180 &&
+ dataset.latlonVals.latMin == -90 && dataset.latlonVals.latMax == 90"
preview-map="dataset" index="$index"></div>
<div ng-show="dataset.latlonVals.lonMin == -180 && dataset.latlonVals.lonMax == 180 &&
dataset.latlonVals.latMin == -90 && dataset.latlonVals.latMax == 90">
@@ -180,14 +180,14 @@
<leaflet-map id="map"></leaflet-map>
</div>
</div>
-
+
<!--Timeline-->
<div class="row">
<div class="col-md-12" ng-controller="TimelineCtrl">
<div class="timeline"></div>
</div>
</div>
-
+
<div class="row">
<div class="col-md-12" ng-controller="ParameterSelectCtrl">
<div class="row top3">
@@ -196,7 +196,7 @@
<form>
<!--This styling HAD to be done inline. Using a class wouldn't work and for some -->
<!--reason the input boxes refused to be 100% wide when their span size was set.-->
- <input ng-disabled="shouldDisableControls()" on-blur="checkParameters();" ng-model="displayParams.start" ui-date="datepickerSettings" ui-date-format="yy-mm-dd" type="text" class="col-md-4 text-center" style="width:100%" />
+ <input ng-disabled="shouldDisableControls()" on-blur="checkParameters();" ng-model="displayParams.start" ui-date="datepickerSettings" type="text" class="col-md-4 text-center" style="width:100%" />
</form>
</div>
<div class="col-md-2 text-center">End Date:</div>
@@ -204,7 +204,7 @@
<form>
<!--This styling HAD to be done inline. Using a class wouldn't work and for some -->
<!--reason the input boxes refused to be 100% wide when their span size was set.-->
- <input ng-disabled="shouldDisableControls()" on-blur="checkParameters();" ng-model="displayParams.end" ui-date="datepickerSettings" ui-date-format="yy-mm-dd" type="text" class="col-md-4 text-center" style="width:100%"/>
+ <input ng-disabled="shouldDisableControls()" on-blur="checkParameters();" ng-model="displayParams.end" ui-date="datepickerSettings" type="text" class="col-md-4 text-center" style="width:100%"/>
</form>
</div>
</div>
diff --git a/ocw-ui/frontend/app/views/modelselect.html b/ocw-ui/frontend/app/views/modelselect.html
index 9bc128f..6c6f8ba 100644
--- a/ocw-ui/frontend/app/views/modelselect.html
+++ b/ocw-ui/frontend/app/views/modelselect.html
@@ -54,7 +54,7 @@
</div>
<div class="control-group">
<label class="control-label" for="lonSelect">Longitude Variable</label>
- <div class"controls">
+ <div class="controls">
<select id="lonSelect">
<option ng-repeat="lon in lonVariables">
{{lon.text}}
diff --git a/ocw-ui/frontend/bower.json b/ocw-ui/frontend/bower.json
index d288a19..a99146e 100644
--- a/ocw-ui/frontend/bower.json
+++ b/ocw-ui/frontend/bower.json
@@ -2,14 +2,14 @@
"name": "ocw-ui",
"version": "0.0.0",
"dependencies": {
- "angular": "1.2.16",
+ "angular": "~1.5.0",
"json3": "~3.3.1",
"es5-shim": "~3.1.0",
"bootstrap": "~3.2.0",
- "angular-resource": "1.2.16",
- "angular-cookies": "1.2.16",
- "angular-animate": "1.2.16",
- "angular-route": "1.2.16",
+ "angular-resource": "~1.5.0",
+ "angular-cookies": "~1.5.0",
+ "angular-animate": "~1.5.0",
+ "angular-route": "~1.5.0",
"angular-ui-router": "angular-ui/ui-router#~0.2.10",
"leaflet": "~0.7.3",
"chap-links-timeline": "~2.6.1",
@@ -18,8 +18,8 @@
"angular-ui-date": "~0.0.3"
},
"devDependencies": {
- "angular-mocks": "1.2.16",
- "angular-scenario": "1.2.16"
+ "angular-mocks": "~1.5.0",
+ "angular-scenario": "~1.5.0"
},
"appPath": "app"
}
diff --git a/ocw-ui/frontend/package.json b/ocw-ui/frontend/package.json
index b0133c8..844c76e 100644
--- a/ocw-ui/frontend/package.json
+++ b/ocw-ui/frontend/package.json
@@ -14,23 +14,26 @@
"generator-angular": "^0.9.5",
"generator-karma": "^0.8.3",
"grunt": "^0.4.1",
+ "grunt-jscs": "^3.0.1",
"grunt-autoprefixer": "^0.7.3",
"grunt-cli": "^0.1.13",
"grunt-concurrent": "^0.5.0",
"grunt-contrib-clean": "^0.5.0",
"grunt-contrib-concat": "^0.4.0",
"grunt-contrib-connect": "^0.7.1",
+ "serve-static": "^1.13.1",
"grunt-contrib-copy": "^0.5.0",
"grunt-contrib-cssmin": "^0.9.0",
"grunt-contrib-htmlmin": "^0.3.0",
"grunt-contrib-imagemin": "^2.0.1",
- "grunt-contrib-jshint": "^0.10.0",
+ "grunt-contrib-jshint": "^1.1.0",
"grunt-contrib-uglify": "^0.4.0",
"grunt-contrib-watch": "^0.6.1",
"grunt-filerev": "^0.2.1",
"grunt-google-cdn": "^0.4.0",
- "grunt-karma": "^0.8.3",
+ "grunt-karma": "^0.12.2",
"grunt-newer": "^0.7.0",
+ "grunt-ngmin": "^0.0.3",
"grunt-ng-annotate": "^0.1.0",
"grunt-svgmin": "^0.4.0",
"grunt-usemin": "^2.1.1",
diff --git a/ocw-ui/frontend/test/spec/services/selecteddatasetinformation.js b/ocw-ui/frontend/test/spec/services/selecteddatasetinformation.js
index 60c1ac2..7dd4a05 100644
--- a/ocw-ui/frontend/test/spec/services/selecteddatasetinformation.js
+++ b/ocw-ui/frontend/test/spec/services/selecteddatasetinformation.js
@@ -71,12 +71,16 @@
it('should provide the removeDataset function', function() {
inject(function(selectedDatasetInformation) {
- selectedDatasetInformation.addDataset(1);
- selectedDatasetInformation.addDataset(2);
- expect(selectedDatasetInformation.getDatasets()[0]).toEqual(1);
+ var dataset_1 = {name: 'dataset_1', shouldDisplay: false, regrid: false};
+ var dataset_2 = {name: 'dataset_2', shouldDisplay: false, regrid: false};
+
+ selectedDatasetInformation.addDataset(dataset_1);
+ selectedDatasetInformation.addDataset(dataset_2);
+
+ expect(selectedDatasetInformation.getDatasets()[0]).toEqual(dataset_1);
selectedDatasetInformation.removeDataset(0);
- expect(selectedDatasetInformation.getDatasets()[0]).toEqual(2);
+ expect(selectedDatasetInformation.getDatasets()[0]).toEqual(dataset_2);
});
});
diff --git a/ocw/dataset.py b/ocw/dataset.py
index 0a0e1a6..bb06443 100644
--- a/ocw/dataset.py
+++ b/ocw/dataset.py
@@ -25,14 +25,12 @@
'''
-import os
-import numpy
-import logging
import datetime as dt
-from mpl_toolkits.basemap import Basemap
-import netCDF4
+import logging
-import ocw
+import netCDF4
+import numpy
+
import ocw.utils as utils
logger = logging.getLogger(__name__)
@@ -235,7 +233,7 @@
class Bounds(object):
- '''Container for holding spatial and temporal bounds information.
+ """Container for holding spatial and temporal bounds information.
Certain operations require valid bounding information to be present for
correct functioning. Bounds guarantees that a function receives well
@@ -245,10 +243,11 @@
* 'rectangular'
* 'CORDEX (CORDEX region name)': pre-defined CORDEX boundary
* 'us_states': an array of US states abbreviation is required (ex) us_states = ['CA','NV'])
- * 'countries': an array of county names is required (ex) countries = ['United States','Canada','Mexico']
+ * 'countries': an array of county names is required (ex) countries = ['United States','Canada']
* 'user': user_mask_file in a netCDF format with two dimensional mask variable is required.
- If boundary_type == 'rectangular', spatial and temporal bounds must follow the following guidelines.
+ If boundary_type == 'rectangular', spatial and temporal bounds must follow the
+ following guidelines.
* Latitude values must be in the range [-90, 90]
* Longitude values must be in the range [-180, 180]
@@ -256,14 +255,15 @@
values.
Temporal bounds must a valid datetime object
- '''
+ """
def __init__(self, boundary_type='rectangular',
us_states=None, countries=None,
- user_mask_file=None, mask_variable_name=None, longitude_name=None, latitude_name=None,
+ user_mask_file=None, mask_variable_name=None,
+ longitude_name=None, latitude_name=None,
lat_min=-90, lat_max=90, lon_min=-180, lon_max=180,
start=None, end=None):
- '''Default Bounds constructor
+ """Default Bounds constructor
:param boundary_type: The type of spatial subset boundary.
:type boundary_type: :mod:`string`
@@ -291,89 +291,132 @@
:type end: :class:`datetime.datetime`
:raises: ValueError
- '''
- self.boundary_type = boundary_type
- if start:
- self._start = start
- else:
- self._start = None
+ """
- if end:
+ self.boundary_type = boundary_type
+
+ self._start = None
+ self._end = None
+ self.lat_min = None
+ self.lat_max = None
+ self.lon_min = None
+ self.lon_max = None
+
+ if start and self._validate_start(start):
+ self._start = start
+
+ if end and self._validate_end(end):
self._end = end
- else:
- self._end = None
if boundary_type == 'us_states':
- self.masked_regions = utils.shapefile_boundary(
- boundary_type, us_states)
+
+ self.masked_regions = utils.shapefile_boundary(boundary_type, us_states)
+
if boundary_type == 'countries':
- self.masked_regions = utils.shapefile_boundary(
- boundary_type, countries)
+
+ self.masked_regions = utils.shapefile_boundary(boundary_type, countries)
+
if boundary_type == 'user':
+
file_object = netCDF4.Dataset(user_mask_file)
self.mask_variable = file_object.variables[mask_variable_name][:]
mask_longitude = file_object.variables[longitude_name][:]
mask_latitude = file_object.variables[latitude_name][:]
if mask_longitude.ndim == 1 and mask_latitude.ndim == 1:
- self.mask_longitude, self.mask_latitude = numpy.meshgrid(
- mask_longitude, mask_latitude)
+ self.mask_longitude, self.mask_latitude = \
+ numpy.meshgrid(mask_longitude, mask_latitude)
elif mask_longitude.ndim == 2 and mask_latitude.ndim == 2:
self.mask_longitude = mask_longitude
self.mask_latitude = mask_latitude
- if boundary_type == 'rectangular':
- if not (-90 <= float(lat_min) <= 90) or float(lat_min) > float(lat_max):
- error = "Attempted to set lat_min to invalid value: %s" % (
- lat_min)
- logger.error(error)
- raise ValueError(error)
- if not (-90 <= float(lat_max) <= 90):
- error = "Attempted to set lat_max to invalid value: %s" % (
- lat_max)
- logger.error(error)
- raise ValueError(error)
- if not (-180 <= float(lon_min) <= 180) or float(lon_min) > float(lon_max):
- error = "Attempted to set lon_min to invalid value: %s" % (
- lon_min)
- logger.error(error)
- raise ValueError(error)
- if not (-180 <= float(lon_max) <= 180):
- error = "Attempted to set lat_max to invalid value: %s" % (
- lon_max)
- logger.error(error)
- raise ValueError(error)
- self.lat_min = float(lat_min)
- self.lat_max = float(lat_max)
- self.lon_min = float(lon_min)
- self.lon_max = float(lon_max)
+ if boundary_type == 'rectangular':
+
+ if self._validate_lat_lon(lat_max=lat_max, lat_min=lat_min, lon_max=lon_max, lon_min=lon_min):
+ self.lat_min = float(lat_min)
+ self.lat_max = float(lat_max)
+ self.lon_min = float(lon_min)
+ self.lon_max = float(lon_max)
+
if boundary_type[:6].upper() == 'CORDEX':
- self.lat_min, self.lat_max, self.lon_min, self.lon_max = utils.CORDEX_boundary(
- boundary_type[6:].replace(" ", "").lower())
+
+ lat_min, lat_max, lon_min, lon_max = \
+ utils.CORDEX_boundary(boundary_type[6:].replace(" ", "").lower())
+
+ if self._validate_lat_lon(lat_max=lat_max, lat_min=lat_min, lon_max=lon_max, lon_min=lon_min):
+ self.lat_min = float(lat_min)
+ self.lat_max = float(lat_max)
+ self.lon_min = float(lon_min)
+ self.lon_max = float(lon_max)
@property
def start(self):
+ """ Getter for start attribute. """
return self._start
@start.setter
def start(self, value):
- if self._end:
- if not (type(value) is dt.datetime and value < self._end):
- error = "Attempted to set start to invalid value: %s" % (value)
- logger.error(error)
- raise ValueError(error)
-
- self._start = value
+ """ Setter for start attribute. """
+ if value and self._validate_start(value):
+ self._start = value
@property
def end(self):
+ """ Getter for end attribute. """
return self._end
@end.setter
def end(self, value):
+ """ Setter for end attribute. """
+ if value and self._validate_end(value):
+ self._end = value
+
+ def _validate_start(self, value):
+ """ Validate start is both the correct type and less than end. """
+ if not isinstance(value, dt.datetime):
+ error = "Attempted to set start to invalid type: %s" % (type(value))
+ logger.error(error)
+ raise ValueError(error)
+
+ if self._end:
+ if value > self._end:
+ error = "Attempted to set start to invalid value: %s" % (value)
+ logger.error(error)
+ raise ValueError(error)
+
+ return True
+
+ def _validate_end(self, value):
+ """ Validate end is both the correct type and greater than start. """
+ if not isinstance(value, dt.datetime):
+ error = "Attempted to set end to invalid type: %s" % (type(value))
+ logger.error(error)
+ raise ValueError(error)
+
if self._start:
- if not (type(value) is dt.datetime and value > self._start):
+ if value < self._start:
error = "Attempted to set end to invalid value: %s" % (value)
logger.error(error)
raise ValueError(error)
- self._end = value
+ return True
+
+ def _validate_lat_lon(self, lat_max, lat_min, lon_max, lon_min):
+ """ Confirm the min / max lat / lon are within expected ranges. """
+ if not (-90 <= float(lat_min) <= 90) or float(lat_min) > float(lat_max):
+ error = "Attempted to set lat_min to invalid value: %s" % (lat_min)
+ logger.error(error)
+ raise ValueError(error)
+ if not -90 <= float(lat_max) <= 90:
+ error = "Attempted to set lat_max to invalid value: %s" % (lat_max)
+ logger.error(error)
+ raise ValueError(error)
+ if not (-180 <= float(lon_min) <= 180) or float(lon_min) > float(lon_max):
+ error = "Attempted to set lon_min to invalid value: %s" % (lon_min)
+ logger.error(error)
+ raise ValueError(error)
+ if not -180 <= float(lon_max) <= 180:
+ error = "Attempted to set lat_max to invalid value: %s" % (lon_max)
+ logger.error(error)
+ raise ValueError(error)
+
+ return True