Repository: ioos/compliance-checker
Pull request #704 (2019-11-19 17:47:14)

Grid mapping attr check rebase
These three commits abstract the attribute type checking, add checks for grid_mapping attributes, and provide a small fixup prior to rebasing.


Patch:
diff --git a/compliance_checker/cf/appendix_f.py b/compliance_checker/cf/appendix_f.py
index 39f6ae0..651505a 100644
--- a/compliance_checker/cf/appendix_f.py
+++ b/compliance_checker/cf/appendix_f.py
@@ -13,47 +13,69 @@ among several mappings, their definitions are contained in a table in the final
 section. The attributes which describe the ellipsoid and prime meridian may be
 included, when applicable, with any grid mapping.
 
+The grid_mapping_attr_types* dictionaries relate type and condition information
+for each of the grid_mapping attributes as defined in the CF standard. Each key-
+value pair is comprised of
+
+    <attribute name> : {type: S|N|D, extra_condition: True|False}
+
+where the 'type' signifies the type that the attribute should be, and the
+'extra_condition' boolean indicates whether to test for an additional condition
+or series of conditions to determine if the value of the attribute is valid.
+
+The grid_mapping_dict* dictionaries map the names of grid mappings to an array
+of length 1, 3, or 4. The array is comprised of tuples:
+
+    [
+        tuple of required attributes,
+        tuple of something that is unclear, (often nothing)
+        tuple of expected standard names,
+        tuple of exclusive attributes
+ 
+    ]
+
 We have used the FGDC "Content Standard for Digital Geospatial Metadata" [FGDC]
 as a guide in choosing the values for grid_mapping_name and the attribute names
 for the parameters describing map projections.
 '''
 
-grid_mapping_names = [
-    'albers_conical_equal_area',
-    'azimuthal_equidistant',
-    'lambert_azimuthal_equal_area',
-    'lambert_conformal_conic',
-    'lambert_cylindrical_equal_area',
-    'latitude_longitude',
-    'mercator',
-    'orthographic',
-    'polar_stereographic',
-    'rotated_latitude_longitude',
-    'stereographic',
-    'transverse_mercator',
-    'vertical_perspective' ]
+grid_mapping_attr_types16 = {
+    'earth_radius': {'type': 'N', 'extra_condition': False},
+    'false_easting': {'type': 'N', 'extra_condition': False},
+    'false_northing': {'type': 'N', 'extra_condition': False},
+    'grid_mapping_name': {'type': 'S', 'extra_condition': False},
+    'grid_north_pole_latitude': {'type': 'N', 'extra_condition': False},
+    'grid_north_pole_longitude': {'type': 'N', 'extra_condition': False},
+    'inverse_flattening': {'type': 'N', 'extra_condition': False},
+    'latitude_of_projection_origin': {'type': 'N', 'extra_condition': True},
+    'longitude_of_central_meridian': {'type': 'N', 'extra_condition': True},
+    'longitude_of_prime_meridian': {'type': 'N', 'extra_condition': True},
+    'longitude_of_projection_origin': {'type': 'N', 'extra_condition': True},
+    'north_pole_grid_longitude': {'type': 'N', 'extra_condition': False},
+    'perspective_point_height': {'type': 'N', 'extra_condition': False},
+    'scale_factor_at_central_meridian': {'type': 'N', 'extra_condition': True},
+    'scale_factor_at_projection_origin': {'type': 'N', 'extra_condition': True},
+    'semi_major_axis': {'type': 'N', 'extra_condition': False},
+    'semi_minor_axis': {'type': 'N', 'extra_condition': False},
+    'standard_parallel': {'type': 'N', 'extra_condition': True},
+    'straight_vertical_longitude_from_pole': {'type': 'N', 'extra_condition': True}
+}
+
+grid_mapping_attr_types17 = grid_mapping_attr_types16.copy() # need shallow copy; update() returns None
+
+grid_mapping_attr_types17.update({
+    'azimuth_of_central_line': {'type': 'N', 'extra_condition': False},
+    'crs_wkt': {'type': 'S', 'extra_condition': False},
+    'geographic_crs_name': {'type': 'S', 'extra_condition': False},
+    'geoid_name': {'type': 'S', 'extra_condition': False},
+    'geopotential_datum_name': {'type': 'S', 'extra_condition': False},
+    'horizontal_datum_name': {'type': 'S', 'extra_condition': True},
+    'prime_meridian_name': {'type': 'S', 'extra_condition': True},
+    'projected_crs_name': {'type': 'S', 'extra_condition': True},
+    'reference_ellipsoid_name': {'type': 'S', 'extra_condition': False},
+    'towgs84': {'type': 'N', 'extra_condition': True}
+    })
 
-grid_mapping_attrs = [
-    'earth_radius',
-    'false_easting',
-    'false_northing',
-    'grid_mapping_name',
-    'grid_north_pole_latitude',
-    'grid_north_pole_longitude',
-    'inverse_flattening',
-    'latitude_of_projection_origin',
-    'longitude_of_central_meridian',
-    'longitude_of_prime_meridian',
-    'longitude_of_projection_origin',
-    'north_pole_grid_longitude',
-    'perspective_point_height',
-    'scale_factor_at_central_meridian',
-    'scale_factor_at_projection_origin',
-    'semi_major_axis',
-    'semi_minor_axis',
-    'standard_parallel',
-    'straight_vertical_longitude_from_pole'
-]
 
 grid_mapping_dict16 = {
     'albers_conical_equal_area': [
@@ -265,3 +287,551 @@ grid_mapping_dict17.update({
         )
     ]
 })
+
+# horizontal datum names from https://github.com/cf-convention/cf-conventions/wiki/csv/horiz_datum.csv
+horizontal_datum_names17 = {
+ 'Hungarian Datum 1909',
+ 'Taiwan Datum 1967',
+ 'Taiwan Datum 1997',
+ 'Iraqi Geospatial Reference System',
+ 'MGI 1901',
+ 'MOLDREF99',
+ 'Reseau Geodesique de la RDC 2005',
+ 'Serbian Reference Network 1998',
+ 'Red Geodesica de Canarias 1995',
+ 'Reseau Geodesique de Mayotte 2004',
+ 'Cadastre 1997',
+ 'Reseau Geodesique de Saint Pierre et Miquelon 2006',
+ 'Autonomous Regions of Portugal 2008',
+ 'Mexican Datum of 1993',
+ 'China 2000',
+ 'Sao Tome',
+ 'New Beijing',
+ 'Principe',
+ 'Reseau de Reference des Antilles Francaises 1991',
+ 'Tokyo 1892',
+ 'System Jednotne Trigonometricke Site Katastralni/05',
+ 'Sri Lanka Datum 1999',
+ 'System Jednotne Trigonometricke Site Katastralni/05 (Ferro)',
+ 'Geocentric Datum Brunei Darussalam 2009',
+ 'Turkish National Reference Frame',
+ 'Bhutan National Geodetic Datum',
+ 'Islands Net 2004',
+ 'International Terrestrial Reference Frame 2008',
+ 'Posiciones Geodesicas Argentinas 2007',
+ 'Marco Geodesico Nacional',
+ 'SIRGAS-Chile',
+ 'Costa Rica 2005',
+ 'Sistema Geodesico Nacional de Panama MACARIO SOLIS',
+ 'Peru96',
+ 'SIRGAS-ROU98',
+ 'SIRGAS_ES2007.8',
+ 'Ocotepeque 1935',
+ 'Sibun Gorge 1922',
+ 'Panama-Colon 1911',
+ 'Reseau Geodesique des Antilles Francaises 2009',
+ 'Corrego Alegre 1961',
+ 'South American Datum 1969(96)',
+ 'Papua New Guinea Geodetic Datum 1994',
+ 'Not specified (based on Airy 1830 ellipsoid)',
+ 'Not specified (based on Airy Modified 1849 ellipsoid)',
+ 'Not specified (based on Australian National Spheroid)',
+ 'Not specified (based on Bessel 1841 ellipsoid)',
+ 'Not specified (based on Bessel Modified ellipsoid)',
+ 'Not specified (based on Bessel Namibia ellipsoid)',
+ 'Not specified (based on Clarke 1858 ellipsoid)',
+ 'Not specified (based on Clarke 1866 ellipsoid)',
+ 'Not specified (based on Clarke 1866 Michigan ellipsoid)',
+ 'Not specified (based on Clarke 1880 (Benoit) ellipsoid)',
+ 'Not specified (based on Clarke 1880 (IGN) ellipsoid)',
+ 'Not specified (based on Clarke 1880 (RGS) ellipsoid)',
+ 'Not specified (based on Clarke 1880 (Arc) ellipsoid)',
+ 'Not specified (based on Clarke 1880 (SGA 1922) ellipsoid)',
+ 'Not specified (based on Everest 1830 (1937 Adjustment) ellipsoid)',
+ 'Not specified (based on Everest 1830 (1967 Definition) ellipsoid)',
+ 'Not specified (based on Everest 1830 Modified ellipsoid)',
+ 'Not specified (based on GRS 1980 ellipsoid)',
+ 'Not specified (based on Helmert 1906 ellipsoid)',
+ 'Not specified (based on Indonesian National Spheroid)',
+ 'Not specified (based on International 1924 ellipsoid)',
+ 'Not specified (based on Krassowsky 1940 ellipsoid)',
+ 'Not specified (based on NWL 9D ellipsoid)',
+ 'Not specified (based on Plessis 1817 ellipsoid)',
+ 'Not specified (based on Struve 1860 ellipsoid)',
+ 'Not specified (based on War Office ellipsoid)',
+ 'Not specified (based on WGS 84 ellipsoid)',
+ 'Not specified (based on GEM 10C ellipsoid)',
+ 'Not specified (based on OSU86F ellipsoid)',
+ 'Not specified (based on OSU91A ellipsoid)',
+ 'Not specified (based on Clarke 1880 ellipsoid)',
+ 'Not specified (based on Authalic Sphere)',
+ 'Not specified (based on GRS 1967 ellipsoid)',
+ 'Not specified (based on Average Terrestrial System 1977 ellipsoid)',
+ 'Not specified (based on Everest (1830 Definition) ellipsoid)',
+ 'Not specified (based on WGS 72 ellipsoid)',
+ 'Not specified (based on Everest 1830 (1962 Definition) ellipsoid)',
+ 'Not specified (based on Everest 1830 (1975 Definition) ellipsoid)',
+ 'Not specified (based on GRS 1980 Authalic Sphere)',
+ 'Not specified (based on Clarke 1866 Authalic Sphere)',
+ 'Not specified (based on International 1924 Authalic Sphere)',
+ 'Not specified (based on Hughes 1980 ellipsoid)',
+ 'Popular Visualisation Datum',
+ 'Greek',
+ 'Greek Geodetic Reference System 1987',
+ 'Average Terrestrial System 1977',
+ 'Kartastokoordinaattijarjestelma (1966)',
+ 'Rikets koordinatsystem 1990',
+ 'Samboja',
+ 'Lithuania 1994 (ETRS89)',
+ 'Tete',
+ 'Madzansua',
+ 'Observatario',
+ 'Moznet (ITRF94)',
+ 'Indian 1960',
+ 'Final Datum 1958',
+ 'Estonia 1992',
+ 'PDO Survey Datum 1993',
+ 'Old Hawaiian',
+ 'St. Lawrence Island',
+ 'St. Paul Island',
+ 'St. George Island',
+ 'Puerto Rico',
+ 'NAD83 Canadian Spatial Reference System',
+ 'Israel',
+ 'Locodjo 1965',
+ 'Abidjan 1987',
+ 'Kalianpur 1937',
+ 'Kalianpur 1962',
+ 'Kalianpur 1975',
+ 'Hanoi 1972',
+ 'Hartebeesthoek94',
+ 'CH1903',
+ 'CH1903+',
+ 'Swiss Terrestrial Reference Frame 1995',
+ 'NAD83 (High Accuracy Reference Network)',
+ 'Rassadiran',
+ 'European Datum 1950(1977)',
+ 'Dabola 1981',
+ 'System Jednotne Trigonometricke Site Katastralni',
+ 'Mount Dillon',
+ 'Naparima 1955',
+ 'European Libyan Datum 1979',
+ 'Chos Malal 1914',
+ 'Pampa del Castillo',
+ 'Korean Datum 1985',
+ 'Yemen National Geodetic Network 1996',
+ 'South Yemen',
+ 'Bissau',
+ 'Korean Datum 1995',
+ 'New Zealand Geodetic Datum 2000',
+ 'Accra',
+ 'American Samoa 1962',
+ 'Sistema de Referencia Geocentrico para America del Sur 1995',
+ 'Reseau Geodesique Francais 1993',
+ 'Posiciones Geodesicas Argentinas',
+ 'IRENET95',
+ 'Sierra Leone Colony 1924',
+ 'Sierra Leone 1968',
+ 'Australian Antarctic Datum 1998',
+ 'Pulkovo 1942(83)',
+ 'Pulkovo 1942(58)',
+ 'Estonia 1997',
+ 'Luxembourg 1930',
+ 'Azores Occidental Islands 1939',
+ 'Azores Central Islands 1948',
+ 'Azores Oriental Islands 1940',
+ 'Madeira 1936',
+ 'OSNI 1952',
+ 'Red Geodesica Venezolana',
+ 'Posiciones Geodesicas Argentinas 1998',
+ 'Albanian 1987',
+ 'Douala 1948',
+ 'Manoca 1962',
+ 'Qornoq 1927',
+ 'Scoresbysund 1952',
+ 'Ammassalik 1958',
+ 'Garoua',
+ 'Kousseri',
+ 'Egypt 1930',
+ 'Pulkovo 1995',
+ 'Adindan',
+ 'Australian Geodetic Datum 1966',
+ 'Australian Geodetic Datum 1984',
+ 'Ain el Abd 1970',
+ 'Afgooye',
+ 'Agadez',
+ 'Lisbon 1937',
+ 'Aratu',
+ 'Arc 1950',
+ 'Arc 1960',
+ 'Batavia',
+ 'Barbados 1938',
+ 'Beduaram',
+ 'Beijing 1954',
+ 'Reseau National Belge 1950',
+ 'Bermuda 1957',
+ 'Bogota 1975',
+ 'Bukit Rimpah',
+ 'Camacupa',
+ 'Campo Inchauspe',
+ 'Cape',
+ 'Carthage',
+ 'Chua',
+ 'Corrego Alegre 1970-72',
+ "Cote d'Ivoire",
+ 'Deir ez Zor',
+ 'Douala',
+ 'Egypt 1907',
+ 'European Datum 1950',
+ 'European Datum 1987',
+ 'Fahud',
+ 'Gandajika 1970',
+ 'Garoua',
+ 'Guyane Francaise',
+ 'Hu Tzu Shan 1950',
+ 'Hungarian Datum 1972',
+ 'Indonesian Datum 1974',
+ 'Indian 1954',
+ 'Indian 1975',
+ 'Jamaica 1875',
+ 'Jamaica 1969',
+ 'Kalianpur 1880',
+ 'Kandawala',
+ 'Kertau 1968',
+ 'Kuwait Oil Company',
+ 'La Canoa',
+ 'Provisional South American Datum 1956',
+ 'Lake',
+ 'Leigon',
+ 'Liberia 1964',
+ 'Lome',
+ 'Luzon 1911',
+ 'Hito XVIII 1963',
+ 'Herat North',
+ 'Mahe 1971',
+ 'Makassar',
+ 'European Terrestrial Reference System 1989',
+ 'Malongo 1987',
+ 'Manoca',
+ 'Merchich',
+ 'Massawa',
+ 'Minna',
+ 'Mhast',
+ 'Monte Mario',
+ "M'poraloko",
+ 'North American Datum 1927',
+ 'NAD27 Michigan',
+ 'North American Datum 1983',
+ 'Nahrwan 1967',
+ 'Naparima 1972',
+ 'New Zealand Geodetic Datum 1949',
+ 'NGO 1948',
+ 'Datum 73',
+ 'Nouvelle Triangulation Francaise',
+ 'NSWC 9Z-2',
+ 'OSGB 1936',
+ 'OSGB 1970 (SN)',
+ 'OS (SN) 1980',
+ 'Padang 1884',
+ 'Palestine 1923',
+ 'Congo 1960 Pointe Noire',
+ 'Geocentric Datum of Australia 1994',
+ 'Pulkovo 1942',
+ 'Qatar 1974',
+ 'Qatar 1948',
+ 'Qornoq',
+ 'Loma Quintana',
+ 'Amersfoort',
+ 'South American Datum 1969',
+ 'Sapper Hill 1943',
+ 'Schwarzeck',
+ 'Segora',
+ 'Serindung',
+ 'Sudan',
+ 'Tananarive 1925',
+ 'Timbalai 1948',
+ 'TM65',
+ 'Geodetic Datum of 1965',
+ 'Tokyo',
+ 'Trinidad 1903',
+ 'Trucial Coast 1948',
+ 'Voirol 1875',
+ 'Bern 1938',
+ 'Nord Sahara 1959',
+ 'Stockholm 1938',
+ 'Yacare',
+ 'Yoff',
+ 'Zanderij',
+ 'Militar-Geographische Institut',
+ 'Reseau National Belge 1972',
+ 'Deutsches Hauptdreiecksnetz',
+ 'Conakry 1905',
+ 'Dealul Piscului 1930',
+ 'Dealul Piscului 1970',
+ 'National Geodetic Network',
+ 'Kuwait Utility',
+ 'World Geodetic System 1972',
+ 'WGS 72 Transit Broadcast Ephemeris',
+ 'World Geodetic System 1984',
+ 'Anguilla 1957',
+ 'Antigua 1943',
+ 'Dominica 1945',
+ 'Grenada 1953',
+ 'Montserrat 1958',
+ 'St. Kitts 1955',
+ 'St. Lucia 1955',
+ 'St. Vincent 1945',
+ 'North American Datum 1927 (1976)',
+ 'North American Datum 1927 (CGQ77)',
+ 'Xian 1980',
+ 'Hong Kong 1980',
+ 'Japanese Geodetic Datum 2000',
+ 'Gunung Segara',
+ 'Qatar National Datum 1995',
+ 'Porto Santo 1936',
+ 'Selvagem Grande',
+ 'South American Datum 1969',
+ 'SWEREF99',
+ 'Point 58',
+ 'Fort Marigot',
+ 'Guadeloupe 1948',
+ 'Centre Spatial Guyanais 1967',
+ 'Reseau Geodesique Francais Guyane 1995',
+ 'Martinique 1938',
+ 'Reunion 1947',
+ 'Reseau Geodesique de la Reunion 1992',
+ 'Tahiti 52',
+ 'Tahaa 54',
+ 'IGN72 Nuku Hiva',
+ 'K0 1949',
+ 'Combani 1950',
+ 'IGN56 Lifou',
+ 'IGN72 Grande Terre',
+ 'ST87 Ouvea',
+ 'Petrels 1972',
+ 'Pointe Geologie Perroud 1950',
+ 'Saint Pierre et Miquelon 1950',
+ 'MOP78',
+ 'Reseau de Reference des Antilles Francaises 1991',
+ 'IGN53 Mare',
+ 'ST84 Ile des Pins',
+ 'ST71 Belep',
+ 'NEA74 Noumea',
+ 'Reseau Geodesique Nouvelle Caledonie 1991',
+ 'Grand Comoros',
+ 'International Terrestrial Reference Frame 1988',
+ 'International Terrestrial Reference Frame 1989',
+ 'International Terrestrial Reference Frame 1990',
+ 'International Terrestrial Reference Frame 1991',
+ 'International Terrestrial Reference Frame 1992',
+ 'International Terrestrial Reference Frame 1993',
+ 'International Terrestrial Reference Frame 1994',
+ 'International Terrestrial Reference Frame 1996',
+ 'International Terrestrial Reference Frame 1997',
+ 'International Terrestrial Reference Frame 2000',
+ 'Reykjavik 1900',
+ 'Hjorsey 1955',
+ 'Islands Net 1993',
+ 'Helle 1954',
+ 'Latvia 1992',
+ 'Porto Santo 1995',
+ 'Azores Oriental Islands 1995',
+ 'Azores Central Islands 1995',
+ 'Lisbon 1890',
+ 'Iraq-Kuwait Boundary Datum 1992',
+ 'European Datum 1979',
+ 'Istituto Geografico Militaire 1995',
+ 'Voirol 1879',
+ 'Chatham Islands Datum 1971',
+ 'Chatham Islands Datum 1979',
+ 'Sistema de Referencia Geocentrico para las AmericaS 2000',
+ 'Guam 1963',
+ 'Vientiane 1982',
+ 'Lao 1993',
+ 'Lao National Datum 1997',
+ 'Jouik 1961',
+ 'Nouakchott 1965',
+ 'Mauritania 1999',
+ 'Gulshan 303',
+ 'Philippine Reference System 1992',
+ 'Gan 1970',
+ 'Gandajika',
+ 'Marco Geocentrico Nacional de Referencia',
+ 'Reseau Geodesique de la Polynesie Francaise',
+ 'Fatu Iva 72',
+ 'IGN63 Hiva Oa',
+ 'Tahiti 79',
+ 'Moorea 87',
+ 'Maupiti 83',
+ 'Nakhl-e Ghanem',
+ 'Posiciones Geodesicas Argentinas 1994',
+ 'Katanga 1955',
+ 'Kasai 1953',
+ 'IGC 1962 Arc of the 6th Parallel South',
+ 'IGN 1962 Kerguelen',
+ 'Le Pouce 1934',
+ 'IGN Astro 1960',
+ 'Institut Geographique du Congo Belge 1955',
+ 'Mauritania 1999',
+ 'Missao Hidrografico Angola y Sao Tome 1951',
+ 'Mhast (onshore)',
+ 'Mhast (offshore)',
+ 'Egypt Gulf of Suez S-650 TL',
+ 'Tern Island 1961',
+ 'Cocos Islands 1965',
+ 'Iwo Jima 1945',
+ 'St. Helena 1971',
+ 'Marcus Island 1952',
+ 'Ascension Island 1958',
+ 'Ayabelle Lighthouse',
+ 'Bellevue',
+ 'Camp Area Astro',
+ 'Phoenix Islands 1966',
+ 'Cape Canaveral',
+ 'Solomon 1968',
+ 'Easter Island 1967',
+ 'Fiji Geodetic Datum 1986',
+ 'Fiji 1956',
+ 'South Georgia 1968',
+ 'Grand Cayman 1959',
+ 'Diego Garcia 1969',
+ 'Johnston Island 1961',
+ 'Little Cayman 1961',
+ 'Midway 1961',
+ 'Pico de las Nieves 1984',
+ 'Pitcairn 1967',
+ 'Santo 1965',
+ 'Viti Levu 1916',
+ 'Marshall Islands 1960',
+ 'Wake Island 1952',
+ 'Tristan 1968',
+ 'Kusaie 1951',
+ 'Deception Island',
+ 'Geocentric datum of Korea',
+ 'Hong Kong 1963',
+ 'Hong Kong 1963(67)',
+ 'Parametrop Zemp 1990',
+ 'Faroe Datum 1954',
+ 'Geodetic Datum of Malaysia 2000',
+ 'Karbala 1979',
+ 'Nahrwan 1934',
+ 'Rauenberg Datum/83',
+ 'Potsdam Datum/83',
+ 'Greenland 1996',
+ 'Vanua Levu 1915',
+ 'Reseau Geodesique de Nouvelle Caledonie 91-93',
+ 'ST87 Ouvea',
+ 'Kertau (RSO)',
+ 'Viti Levu 1912',
+ 'fk89',
+ 'Libyan Geodetic Datum 2006',
+ 'Datum Geodesi Nasional 1995',
+ 'Vietnam 2000',
+ 'SVY21',
+ 'Jamaica 2001',
+ 'NAD83 (National Spatial Reference System 2007)',
+ 'World Geodetic System 1966',
+ 'Croatian Terrestrial Reference System',
+ 'Bermuda 2000',
+ 'Pitcairn 2006',
+ 'Ross Sea Region Geodetic Datum 2000',
+ 'Slovenia Geodetic Datum 1996',
+ 'CH1903 (Bern)',
+ 'Bogota 1975 (Bogota)',
+ 'Lisbon 1937 (Lisbon)',
+ 'Makassar (Jakarta)',
+ 'Militar-Geographische Institut (Ferro)',
+ 'Monte Mario (Rome)',
+ 'Nouvelle Triangulation Francaise (Paris)',
+ 'Padang 1884 (Jakarta)',
+ 'Reseau National Belge 1950 (Brussels)',
+ 'Tananarive 1925 (Paris)',
+ 'Voirol 1875 (Paris)',
+ 'Batavia (Jakarta)',
+ 'Stockholm 1938 (Stockholm)',
+ 'Greek (Athens)',
+ 'Carthage (Paris)',
+ 'NGO 1948 (Oslo)',
+ 'System Jednotne Trigonometricke Site Katastralni (Ferro)',
+ 'Nord Sahara 1959 (Paris)',
+ 'Gunung Segara (Jakarta)',
+ 'Voirol 1879 (Paris)',
+ 'International Terrestrial Reference Frame 2005',
+ 'Ancienne Triangulation Francaise (Paris)',
+ 'Nord de Guerre (Paris)',
+ 'Madrid 1870 (Madrid)',
+ 'Lisbon 1890 (Lisbon)'}
+
+# prime meridian_names from https://github.com/cf-convention/cf-conventions/wiki/csv/prime_meridian.csv
+prime_meridian_names17 = {
+ 'Athens',
+ 'Bern',
+ 'Bogota',
+ 'Brussels',
+ 'Ferro',
+ 'Greenwich',
+ 'Jakarta',
+ 'Lisbon',
+ 'Madrid',
+ 'Oslo',
+ 'Paris',
+ 'Paris RGS',
+ 'Rome',
+ 'Stockholm'}
+ 
+
+# ellipsoid names from https://github.com/cf-convention/cf-conventions/wiki/csv/ellipsoid.csv
+ellipsoid_names17 = {
+ 'Airy 1830',
+ 'Airy Modified 1849',
+ 'Australian National Spheroid',
+ 'Average Terrestrial System 1977',
+ 'Bessel 1841',
+ 'Bessel Modified',
+ 'Bessel Namibia',
+ 'Bessel Namibia (GLM)',
+ 'CGCS2000',
+ 'Clarke 1858',
+ 'Clarke 1866',
+ 'Clarke 1866 Authalic Sphere',
+ 'Clarke 1866 Michigan',
+ 'Clarke 1880',
+ 'Clarke 1880 (Arc)',
+ 'Clarke 1880 (Benoit)',
+ 'Clarke 1880 (IGN)',
+ 'Clarke 1880 (RGS)',
+ 'Clarke 1880 (SGA 1922)',
+ 'Clarke 1880 (international foot)',
+ 'Danish 1876',
+ 'Everest (1830 Definition)',
+ 'Everest 1830 (1937 Adjustment)',
+ 'Everest 1830 (1962 Definition)',
+ 'Everest 1830 (1967 Definition)',
+ 'Everest 1830 (1975 Definition)',
+ 'Everest 1830 (RSO 1969)',
+ 'Everest 1830 Modified',
+ 'GEM 10C',
+ 'GRS 1967',
+ 'GRS 1967 Modified',
+ 'GRS 1980',
+ 'GRS 1980 Authalic Sphere',
+ 'Helmert 1906',
+ 'Hough 1960',
+ 'Hughes 1980',
+ 'IAG 1975',
+ 'Indonesian National Spheroid',
+ 'International 1924',
+ 'International 1924 Authalic Sphere',
+ 'Krassowsky 1940',
+ 'NWL 9D',
+ 'OSU86F',
+ 'OSU91A',
+ 'PZ-90',
+ 'Plessis 1817',
+ 'Popular Visualisation Sphere',
+ 'Sphere',
+ 'Struve 1860',
+ 'WGS 72',
+ 'WGS 84',
+ 'War Office'}
+ 
diff --git a/compliance_checker/cf/cf.py b/compliance_checker/cf/cf.py
index c466aea..e3c0234 100644
--- a/compliance_checker/cf/cf.py
+++ b/compliance_checker/cf/cf.py
@@ -5,7 +5,9 @@ from compliance_checker.base import BaseCheck, BaseNCCheck, Result, TestCtx
 from compliance_checker.cf.appendix_d import (dimless_vertical_coordinates_1_6, dimless_vertical_coordinates_1_7,
                                               no_missing_terms)
 from compliance_checker.cf.appendix_e import cell_methods16, cell_methods17
-from compliance_checker.cf.appendix_f import grid_mapping_dict16, grid_mapping_dict17
+from compliance_checker.cf.appendix_f import (grid_mapping_dict16, grid_mapping_dict17,
+                                              grid_mapping_attr_types16, grid_mapping_attr_types17,
+                                              horizontal_datum_names17, ellipsoid_names17, prime_meridian_names17)
 from compliance_checker.cf import util
 from compliance_checker import cfutil
 from cf_units import Unit
@@ -385,6 +387,34 @@ class CFBaseCheck(BaseCheck):
 
         return valid_formula_terms.to_result()
 
+    # TODO, move this into alphabetical order?
+    def check_grid_mapping_attr_type(self, attr_name, attr, attr_type, variable=None):
+        """
+        Check the type of an attribute. Wrapper function, designed to
+        be overloaded in concrete implementations.
+
+        :param str attr_name: name of attribute
+        :param attr: attribute to check
+        :param str attr_type: designated type of attr; 'S', 'N', or 'D'
+        :param netCDF4.Variable variable
+        :rtype tuple
+        :return two-tuple of (bool, str|None)
+        """
+        raise NotImplementedError
+
+    def check_grid_mapping_attr_condition(self, attr, attr_name, ret_val):
+        """
+        Evaluate a condition (or series of conditions) for a particular
+        attribute. Designed to be overloaded in subclass implementations.
+
+        :param attr: attribute to teset condition for
+        :param str attr_name: name of the attribute
+        :param list ret_val: list of results to append to
+        :rtype None
+        :return None
+        """
+        raise NotImplementedError
+
     def _dims_in_order(self, dimension_order):
         '''
         :param list dimension_order: A list of axes
@@ -907,6 +937,57 @@ class CFBaseCheck(BaseCheck):
 
         return results
 
+    def _check_attr_type(self, attribute, attr_type, variable=None):
+        """
+        Check if an attribute `attr` is of the type `attr_type`. Upon getting
+        a data type of 'D', the attr must have the same data type as the
+        variable it is assigned to.
+
+        Attributes designated type 'S' must be of type `str`. 'N' require
+        numeric types, and 'D' requires the attribute type match the type
+        of the variable it is assigned to.
+
+        :param attribute: attribute to check
+        :param str attr_type: the correct type of the attribute
+        :param variable: if given, type should match attr
+        :rtype list
+        :return A two-element list that contains pass/fail status as a boolean and
+                a message string (or None if unset) as the second element.
+                The string will contain a format field to be filled with a
+                string describing the attribute.
+        """
+
+        if attr_type == 'S':
+            if not isinstance(attribute, basestring):
+                return [False,
+                        "{} must be a string"]
+        else:
+            # if it's not a string, it should have a numpy dtype
+            underlying_dtype = getattr(attribute, 'dtype', None)
+
+            # TODO check for np.nan separately
+            if underlying_dtype is None:
+                return [False,
+                        "{} must be a numeric type"]
+
+            # both D and N should be some kind of numeric value
+            is_numeric = np.issubdtype(underlying_dtype, np.number)
+            if attr_type == 'N':
+                if not is_numeric:
+                    return [False,
+                            "{} must be a numeric type"]
+            elif attr_type == 'D':
+                # TODO: handle edge case where variable is unset here
+                var_dtype = getattr(variable, 'dtype', None)
+                if (underlying_dtype != var_dtype):
+                    return [False,
+                            "{{}} must be numeric and must match {} dtype".format(var_dtype)]
+            else:
+                # If we reached here, we fell off with an unrecognized type
+                return [False, "{{}} has unrecognized type '{}'".format(attr_type)]
+        # pass if all other possible failure conditions have been evaluated
+        return [True, None]
+
     def _handle_dtype_check(self, attribute, attr_name, attr_dict,
                             variable=None):
         """
@@ -916,6 +997,7 @@ class CFBaseCheck(BaseCheck):
         :param str attr_name: The name of the attribute being processed
         :param dict attr_dict: The dict entry with type and attribute location
                                information corresponding to this attribute
+        :param variable: if given, the variable whose type to check against
         :rtype: tuple
         :return: A two-tuple that contains pass/fail status as a boolean and
                  a message string (or None if unset) as the second element.
@@ -928,34 +1010,16 @@ class CFBaseCheck(BaseCheck):
                     if 'G' in attr_dict['attr_loc'] and variable is None
                     else "Attribute {} in variable {}".format(attr_name,
                                                               variable.name))
-        if attr_type == 'S':
-            if not isinstance(attribute, basestring):
-                return (False,
-                        "{} must be a string".format(attr_str))
-        else:
-            # if it's not a string, it should have a numpy dtype
-            underlying_dtype = getattr(attribute, 'dtype', None)
-            if underlying_dtype is None:
-                return (False,
-                        "{} must be a numeric type".format(attr_str))
-            # both D and N should be some kind of numeric value
-            is_numeric = np.issubdtype(underlying_dtype, np.number)
-            if attr_type == 'N':
-                if not is_numeric:
-                    return (False,
-                            "{} must be numeric".format(attr_str))
-            elif attr_type == 'D':
-                # TODO: handle edge case where variable is unset here
-                var_dtype = getattr(variable, 'dtype', None)
-                if not is_numeric or (underlying_dtype != var_dtype):
-                    return (False,
-                            "{} must be numeric and must match".format(attr_str))
-            else:
-                # If we reached here, we fell off with an unrecognized type
-                return("{} has unrecognized type '{}'".format(attr_str,
-                                                              attr_type))
-        # pass if all other possible failure conditions have been evaluated
-        return (True, None)
+
+        # check the type
+        return_value = self._check_attr_type(attribute, attr_type, variable)
+
+        # if the second element is a string, format it
+        if isinstance(return_value[1], basestring):
+            return_value[1] = return_value[1].format(attr_str)
+
+        # convert to tuple for immutability and return
+        return tuple(return_value)
 
 class CFNCCheck(BaseNCCheck, CFBaseCheck):
     """Inherits from both BaseNCCheck and CFBaseCheck to support
@@ -1035,6 +1099,7 @@ class CF1_6Check(CFNCCheck):
 
         self.cell_methods = cell_methods16
         self.grid_mapping_dict = grid_mapping_dict16
+        self.grid_mapping_attr_types = grid_mapping_attr_types16
 
     ###############################################################################
     # Chapter 2: NetCDF Files and Components
@@ -2738,6 +2803,189 @@ class CF1_6Check(CFNCCheck):
 
         return ret_val
 
+    def check_grid_mapping_attr_type(self, attr_name, attr, attr_type, variable=None):
+        """
+        Check the type of an attribute. Wrapper function,
+        implemented for CF-1.6.
+
+        :param str attr_name: name of attribute
+        :param attr: attribute to check
+        :param str attr_type: designated type of attr; 'S', 'N', or 'D'
+        :param netCDF4.Variable variable
+        :rtype tuple
+        :return two-tuple of (bool, str|None)
+        """
+
+        # get check result, list [bool, str|None]
+        _res = self._check_attr_type(attr, attr_type, variable)
+
+        # fully format the message if it needs it
+        if isinstance(_res[1], basestring):
+            _res[1] = _res[1].format(attr_name)
+
+        # tuple for immutability
+        return tuple(_res)
+
+    def check_grid_mapping_attr_condition(self, attr, attr_name):
+        """
+        Evaluate a condition (or series of conditions) for a particular
+        attribute. Implementation for CF-1.6.
+
+        :param attr: attribute to teset condition for
+        :param str attr_name: name of the attribute
+        :rtype tuple
+        :return two-tuple of (bool, str)
+        """
+
+        if attr_name == 'latitude_of_projection_origin':
+            return self._evaluate_latitude_of_projection_origin(attr)
+
+        elif attr_name == 'longitude_of_projection_origin':
+            return self._evaluate_longitude_of_projection_origin(attr)
+
+        elif attr_name == 'longitude_of_central_meridian':
+            return self._evaluate_longitude_of_central_meridian(attr)
+
+        elif attr_name == 'longitude_of_prime_meridian':
+            return self._evaluate_longitude_of_prime_meridian(attr)
+
+        elif attr_name == 'scale_factor_at_central_meridian':
+            return self._evaluate_scale_factor_at_central_meridian(attr)
+
+        elif attr_name == 'scale_factor_at_projection_origin':
+            return self._evaluate_scale_factor_at_projection_origin(attr)
+
+        elif attr_name == 'standard_parallel':
+            return self._evaluate_standard_parallel(attr)
+
+        elif attr_name == 'straight_vertical_longitude_from_pole':
+            return self._evaluate_straight_vertical_longitude_from_pole(attr)
+
+        else:
+            raise NotImplementedError(
+                'Evaluation for {} not yet implemented'.format(attr_name)
+            )
+
+    def _evaluate_latitude_of_projection_origin(self, val):
+        """
+        Evaluate the condition for `latitude_of_projection_origin` attribute.
+        Return result. Value must be -90 <= x <= 90.
+
+        :param val: value to be tested
+        :rtype tuple
+        :return two-tuple (bool, msg)
+        """
+
+        return (
+            (val >= -90.) and (val <= 90.),
+            'latitude_of_projection_origin must satisfy (-90 <= x <= 90)'
+        )
+
+    def _evaluate_longitude_of_projection_origin(self, val):
+        """
+        Evaluate the condition for `longitude_of_projection_origin` attribute.
+        Return result.
+
+        :param val: value to be tested
+        :rtype tuple
+        :return two-tuple (bool, msg)
+        """
+
+        return (
+            (val >= -180.) and (val <= 180.),
+            'longitude_of_projection_origin must satisfy (-180 <= x <= 180)'
+        )
+
+    def _evaluate_longitude_of_central_meridian(self, val):
+        """
+        Evaluate the condition for `longitude_of_central_meridian` attribute.
+        Return result.
+
+        :param val: value to be tested
+        :rtype tuple
+        :return two-tuple (bool, msg)
+        """
+
+        return (
+            (val >= -180.) and (val <= 180.),
+            'longitude_of_central_meridian must satisfy (-180 <= x <= 180)'
+        )
+
+    def _evaluate_longitude_of_prime_meridian(self, val):
+        """
+        Evaluate the condition for `longitude_of_prime_meridian` attribute.
+        Return result.
+
+        :param val: value to be tested
+        :rtype tuple
+        :return two-tuple (bool, msg)
+        """
+
+        return (
+            (val >= -180.) and (val <= 180.),
+            'longitude_of_prime_meridian must satisfy (-180 <= x <= 180)'
+        )
+
+    def _evaluate_scale_factor_at_central_meridian(self, val):
+        """
+        Evaluate the condition for `scale_factor_at_central_meridian` attribute.
+        Return result.
+
+        :param val: value to be tested
+        :rtype tuple
+        :return two-tuple (bool, msg)
+        """
+
+        return (
+            val > 0.0,
+            'scale_factor_at_central_meridian must be > 0.0'
+        )
+
+    def _evaluate_scale_factor_at_projection_origin(self, val):
+        """
+        Evaluate the condition for `scale_factor_at_projection_origin` attribute.
+        Return result.
+
+        :param val: value to be tested
+        :rtype tuple
+        :return two-tuple (bool, msg)
+        """
+
+        return (
+            val > 0.0,
+            'scale_factor_at_projection_origin must be > 0.0'
+        )
+
+    def _evaluate_standard_parallel(self, val):
+        """
+        Evaluate the condition for `standard_parallel` attribute. Return result.
+
+        :param val: value to be tested
+        :rtype tuple
+        :return two-tuple (bool, msg)
+        """
+
+        return (
+            (val >= -90.) and (val <= 90),
+            'standard_parallel must satisfy (-90 <= x <= 90)'
+        )
+
+
+    def _evaluate_straight_vertical_longitude_from_pole(self, val):
+        """
+        Evaluate the condition for `straight_vertical_longitude_from_pole`
+        attribute. Return result.
+
+        :param val: value to be tested
+        :rtype tuple
+        :return two-tuple (bool, msg)
+        """
+
+        return (
+            (val >= -180.) and (val <= 180),
+            'straight_vertical_longitude_from_pole must satisfy (-180 <= x <= 180)'
+        )
+
     # grid mapping dictionary, appendix F
 
 
@@ -3661,6 +3909,7 @@ class CF1_7Check(CF1_6Check):
 
         self.cell_methods = cell_methods17
         self.grid_mapping_dict = grid_mapping_dict17
+        self.grid_mapping_attr_types = grid_mapping_attr_types17
 
     def check_actual_range(self, ds):
         """Check the actual_range attribute of variables. As stated in
@@ -3921,6 +4170,288 @@ class CF1_7Check(CF1_6Check):
 
         return ret_val
 
+    def check_grid_mapping_attr_condition(self, attr, attr_name):
+        """
+        Evaluate a condition (or series of conditions) for a particular
+        attribute. Implementation for CF-1.7.
+
+        :param attr: attribute to teset condition for
+        :param str attr_name: name of the attribute
+        :rtype tuple
+        :return two-tuple of (bool, str)
+        """
+
+        if attr_name == 'geographic_crs_name':
+            return self._evaluate_geographic_crs_name(attr)
+
+        elif attr_name == 'geoid_name':
+            return self._evaluate_geoid_name(attr)
+
+        elif attr_name == 'geopotential_datum_name':
+            return self._evaluate_geopotential_datum_name(attr)
+
+        elif attr_name == 'horizontal_datum_name':
+            return self._evaluate_horizontal_datum_name(attr)
+
+        elif attr_name == 'prime_meridian_name':
+            return self._evaluate_prime_meridian_name(attr)
+
+        elif attr_name == 'projected_crs_name':
+            return self._evaluate_projected_crs_name(attr)
+
+        elif attr_name == 'reference_ellipsoid_name':
+            return self._evaluate_reference_ellipsoid_name(attr)
+
+        elif attr_name == 'towgs84':
+            return self._evaluate_towgs84(attr)
+
+        else: # invoke method from 1.6, as these names are all still valid
+            return super(CF1_7Check, self).check_grid_mapping_attr_condition(attr, attr_name)
+
+    def _check_gmattr_existence_condition_geoid_name_geoptl_datum_name(self, var):
+        """
+        Check to see if both geoid_name and geopotential_datum_name exist as attributes
+        for `var`. They should not.
+
+        :param netCDF4.Variable var
+        :rtype tuple
+        :return two-tuple (bool, str)
+        """
+
+        msg = 'Both geoid_name and geopotential_datum_name cannot exist'
+
+        if ('geoid_name' in var.ncattrs()) and ('geopotential_datum_name' in var.ncattrs()):
+            return (False, msg)
+
+        else:
+            return (True, msg)
+
+    def _check_gmattr_existence_condition_ell_pmerid_hdatum(self, var):
+        """
+        If one of reference_ellipsoid_name, prime_meridian_name, or
+        horizontal_datum_name are defined as grid_mapping attributes,
+        they must all be defined.
+
+        :param netCDF4.Variable var
+        :rtype tuple
+        :return two-tuple (bool, str)
+        """
+
+        msg = ("If any of reference_ellipsoid_name, prime_meridian_name, "
+               "or horizontal_datum_name are defined, all must be defined.")
+
+        _ncattrs = set(var.ncattrs())
+
+        if any(
+            [
+                x in _ncattrs for x in [
+                    'reference_ellipsoid_name',
+                    'prime_meridian_name',
+                    'horizontal_datum_name'
+                ]
+            ]) and (not set(
+                [
+                    'reference_ellipsoid_name',
+                    'prime_meridian_name',
+                    'horizontal_datum_name'
+                ]
+            ).issubset(_ncattrs)):
+            return (False, msg)
+
+        else:
+            return (True, msg)
+
+
+    def _get_projdb_conn(self):
+        """
+        Return a SQLite Connection to the PROJ database.
+
+        Returns:
+            sqlite3.Connection
+        """
+
+        proj_db_path = os.path.join(pyproj.datadir.get_data_dir(), 'proj.db')
+        return sqlite3.connect(proj_db_path)
+
+    def _exec_query_str_with_params(self, qstr, argtuple):
+        """
+        Execute a query string in a database connection with the given argument
+        tuple. Return a result set.
+
+        :param str qstr: desired query to be exectued
+        :param tuple argtuple: tuple of arguments to be supplied to query
+        :rtype set
+        """
+
+        conn = self._get_projdb_conn()
+        return conn.execute(qstr, argtuple)
+
+    def _evaluate_geographic_crs_name(self, val):
+        """
+        Evalute the condition for the geographic_crs_name attribute.
+
+        :param val: value to be tested
+        :rtype tuple
+        :return two-tuple of (bool, str)
+        """
+
+        query_str = ('SELECT 1 FROM geodetic_crs WHERE name = ? '
+                     'UNION ALL ' # need union in case contained in other tables
+                     'SELECT 1 FROM alias_name WHERE alt_name = ? '
+                     'AND table_name = \'geodetic_crs\' LIMIT 1')
+
+        # try to find the value in the database
+        res_set = self._exec_query_str_with_params(query_str, (val, val))
+
+        # does it exist? if so, amt returned  be > 1
+        return (
+            len(res_set.fetchall()) > 0,
+            'geographic_crs_name must correspond to a valid OGC WKT GEOGCS name'
+        )
+
+    def _evaluate_geoid_name(self, val):
+        """
+        Evalute the condition for the geod_name attribute.
+
+        :param val: value to be tested
+        :rtype tuple
+        :return two-tuple of (bool, str)
+        """
+
+        query_str = ('SELECT 1 FROM vertical_datum WHERE name = ? '
+                     'UNION ALL '
+                     'SELECT 1 FROM alias_name WHERE alt_name = ? '
+                     'AND table_name = \'vertical_datum\' LIMIT 1')
+
+        # try to find the value in the database
+        res_set = self._exec_query_str_with_params(query_str, (val, val))
+
+        return (
+            len(res_set.fetchall()) > 0,
+            'geoid_name must correspond to a valid OGC WKT VERT_DATUM name'
+        )
+
+    def _evaluate_geopotential_datum_name(self, val):
+        """
+        Evalute the condition for the geogpotential_datum_name attribute.
+
+        :param val: value to be tested
+        :rtype tuple
+        :return two-tuple of (bool, str)
+        """
+
+        query_str = ('SELECT 1 FROM vertical_datum WHERE name = ? '
+                     'UNION ALL '
+                     'SELECT 1 FROM alias_name WHERE alt_name = ? '
+                     'AND table_name = \'vertical_datum\' LIMIT 1')
+
+        # try to find the value in the database
+        res_set = self._exec_query_str_with_params(query_str, (val, val))
+
+        return (
+            len(res_set.fetchall()) > 0,
+            'geopotential_datum_name must correspond to a valid OGC WKT VERT_DATUM name'
+        )
+
+    def _evaluate_horizontal_datum_name(self, val):
+        """
+        Evalute the condition for the horizontal_datum_name attribute.
+
+        :param val: value to be tested
+        :rtype tuple
+        :return two-tuple of (bool, str)
+        """
+
+        return (
+            val in horizontal_datum_names17,
+            ('{} must be a valid Horizontal Datum Name; '
+
+             'see https://github.com/cf-convention/cf-conventions/wiki/Mapping-from-CF-Grid-Mapping-Attributes-to-CRS-WKT-Elements.')
+        )
+
+    def _evaluate_prime_meridian_name(self, val):
+        """
+        Evaluate the condition for the prime_meridian_name.
+
+        :param val: value to be tested
+        :rtype tuple
+        :return two-tuple of (bool, str)
+        """
+
+        return (
+            val in prime_meridian_names17,
+            ('{} must be a valid Prime Meridian name; '
+             'see https://github.com/cf-convention/cf-conventions/wiki/csv/prime_meridian.csv.')
+        )
+
+    def _evaluate_projected_crs_name(self, val):
+        """
+        Evaluate the condition for the projected_crs attribute.
+
+        :param val: value to be tested
+        :rtype tuple
+        :return two-tuple of (bool, str)
+        """
+
+        query_str = ('SELECT 1 FROM projected_crs WHERE name = ? '
+                     'UNION ALL '
+                     'SELECT 1 FROM alias_name WHERE alt_name = ? '
+                     'AND table_name = \'projected_crs\' LIMIT 1')
+
+        # try to find the value in the database
+        res_set = self._exec_query_str_with_params(query_str, (val, val))
+
+        return (
+            len(res_set.fetchall()) > 0,
+            'projected_crs_name must correspond to a valid OGC WKT PROJCS name'
+        )
+
+    def _evaluate_reference_ellipsoid_name(self, val):
+        """
+        Evaluate the condition for the reference_ellipsoid_name attribute.
+
+        :param val: value to be tested
+        :rtype tuple
+        :return two-tuple of (bool, str)
+        """
+
+        return (
+            val in ellipsoid_names17,
+            ('{} must be a valid Ellipsoid Name; '
+             'see https://github.com/cf-convention/cf-conventions/wiki/csv/ellipsoid.csv.')
+        )
+
+    def _evaluate_towgs84(self, val):
+        """
+        Evaluate the condition for the towgs84 attribute.
+
+        :param val: value to be tested
+        :rtype tuple
+        :return two-tuple of (bool, str)
+        """
+
+        msg = ('towgs84 must be an array of length 3, 6, or 7 of double-precision'
+               ' and correspond to anm OGC WKT TOWGS84 node')
+
+        # if not numpy type, return false
+        if not getattr(val, 'dtype', None):
+            return (False, msg)
+
+        # must be double-precision array
+        elif val.dtype != np.float64:
+            return (False, msg)
+
+        # must be of length 3, 6, or 7
+        elif not val.shape: # single value
+            return (False, msg)
+
+        elif (not (val.size in (3, 6, 7))):
+            return (False, msg)
+
+        else:
+            return (True, msg)
+
+
     def check_grid_mapping(self, ds):
         __doc__ = super(CF1_7Check, self).check_grid_mapping.__doc__
         prev_return = super(CF1_7Check, self).check_grid_mapping(ds)
@@ -3949,6 +4480,13 @@ class CF1_7Check(CF1_6Check):
                         test_ctx.score += 1
                     test_ctx.out_of += 1
 
+            # existence_conditions
+            exist_cond_1 = self._check_gmattr_existence_condition_geoid_name_geoptl_datum_name(var)
+            test_ctx.assert_true(
+                exist_cond_1[0], exist_cond_1[1])
+            exist_cond_2 = self._check_gmattr_existence_condition_ell_pmerid_hdatum(var)
+            test_ctx.assert_true(
+                exist_cond_2[0], exist_cond_2[1])
 
             # handle vertical datum related grid_mapping attributes
             vert_datum_attrs = {}
@@ -3964,7 +4502,7 @@ class CF1_7Check(CF1_6Check):
                 test_ctx.messages.append("Cannot have both 'geoid_name' and "
                                      "'geopotential_datum_name' attributes in "
                                      "grid mapping variable '{}'".format(
-                                          var_name))
+                                          var.name))
             elif len_vdatum_name_attrs == 1:
                     # should be one or zero attrs
                     proj_db_path = os.path.join(pyproj.datadir.get_data_dir(),
@@ -3980,14 +4518,14 @@ class CF1_7Check(CF1_6Check):
                                            "attribute '{}' in grid mapping "
                                            "variable '{}' is not valid".format(
                                                 v_datum_value, v_datum_attr,
-                                                var_name))
+                                                var.name))
                             test_ctx.assert_true(v_datum_str_valid, invalid_msg)
                     except sqlite3.Error as e:
                         # if we hit an error, skip the check
                         warn("Error occurred while trying to query "
                                         "Proj4 SQLite database at {}: {}"
                                         .format(proj_db_path, str(e)))
-            prev_return[var_name] = test_ctx.to_result()
+            prev_return[var.name] = test_ctx.to_result()
 
         return prev_return
 


Test patch:
diff --git a/compliance_checker/tests/test_cf.py b/compliance_checker/tests/test_cf.py
index c39b92d..f318a61 100644
--- a/compliance_checker/tests/test_cf.py
+++ b/compliance_checker/tests/test_cf.py
@@ -905,6 +905,105 @@ class TestCF1_6(BaseTestCase):
         expected_name = u'§5.6 Horizontal Coorindate Reference Systems, Grid Mappings, Projections'
         assert all(r.name == expected_name for r in results.values())
 
+    def test_check_grid_mapping_attr_type(self):
+        """
+        Check that the check_grid_mapping_attr_type method checks
+        grid_mapping attribute types correctly.
+        """
+
+        # test good
+        att_name = 'test_att'
+        att = np.int64(45)
+        att_type = 'N' # numeric
+        res = self.cf.check_grid_mapping_attr_type(att_name, att, att_type)
+        self.assertTrue(res[0])
+        self.assertEqual(res[1], None)
+
+        att_name = 'test_att'
+        att = np.int64(45)
+        x = np.int64(45) # the variable
+        att_type = 'D' # numeric, types should match
+        res = self.cf.check_grid_mapping_attr_type(att_name, att, att_type, x)
+        self.assertTrue(res[0])
+        self.assertEqual(res[1], None)
+
+        att_name = 'test_att'
+        att = 'yo'
+        att_type = 'S' # string
+        res = self.cf.check_grid_mapping_attr_type(att_name, att, att_type)
+        self.assertTrue(res[0])
+        self.assertEqual(res[1], None)
+
+        att_name = 'test_att'
+        att = np.float64(45)
+        att_type = 'D' # string, types should match
+        res = self.cf.check_grid_mapping_attr_type(att_name, att, att_type, np.float64(95))
+        self.assertTrue(res[0])
+        self.assertEqual(res[1], None)
+        
+        # test bad
+        att_name = 'test_att'
+        att = np.int64(45)
+        att_type = 'S' # string, but att type is numeric
+        res = self.cf.check_grid_mapping_attr_type(att_name, att, att_type)
+        self.assertFalse(res[0])
+        self.assertEqual(res[1], "test_att must be a string")
+
+        # test bad
+        att_name = 'test_att'
+        att = 'bad'
+        att_type = 'N' # numeric, but att type is string
+        res = self.cf.check_grid_mapping_attr_type(att_name, att, att_type)
+        self.assertFalse(res[0])
+        self.assertEqual(res[1], "test_att must be a numeric type")
+
+        att_name = 'test_att'
+        att = np.int32(2)
+        att_type = 'D' # should be same datatypes
+        res = self.cf.check_grid_mapping_attr_type(att_name, att, att_type, np.float32(2))
+        self.assertFalse(res[0])
+        self.assertEqual(res[1], "test_att must be numeric and must match float32 dtype")
+
+    def test_check_grid_mapping_attr_condition(self):
+        """
+        Ensure the check_grid_mapping_attr_condition() method works as expected.
+        """
+
+        # test passes
+        attr_name = 'latitude_of_projection_origin'
+        val = 0
+        res = self.cf.check_grid_mapping_attr_condition(val, attr_name)
+        self.assertTrue(res[0])
+
+        attr_name = 'longitude_of_projection_origin'
+        val = 0
+        res = self.cf.check_grid_mapping_attr_condition(val, attr_name)
+        self.assertTrue(res[0])
+
+        attr_name = 'longitude_of_prime_meridian'
+        val = 0
+        res = self.cf.check_grid_mapping_attr_condition(val, attr_name)
+        self.assertTrue(res[0])
+
+        attr_name = 'scale_factor_at_central_meridian'
+        val = 1
+        res = self.cf.check_grid_mapping_attr_condition(val, attr_name)
+        self.assertTrue(res[0])
+
+        attr_name = 'scale_factor_at_projection_origin'
+        val = 1
+        res = self.cf.check_grid_mapping_attr_condition(val, attr_name)
+        self.assertTrue(res[0])
+
+        attr_name = 'standard_parallel'
+        val = 0
+        res = self.cf.check_grid_mapping_attr_condition(val, attr_name)
+        self.assertTrue(res[0])
+
+        attr_name = 'straight_vertical_longitude_from_pole'
+        val = 0
+        res = self.cf.check_grid_mapping_attr_condition(val, attr_name)
+        self.assertTrue(res[0])
 
     def test_check_geographic_region(self):
         dataset = self.load_dataset(STATIC_FILES['bad_region'])
@@ -1562,3 +1661,206 @@ class TestCF1_7(BaseTestCase):
         assert len(results) == 3
         assert scored < out_of
         assert all(r.name == u"§4.3 Vertical Coordinate" for r in results)
+
+    def test_check_attr_type(self):
+        '''
+         Ensure the _check_attr_type method works as expected.
+        '''
+
+        # first, test all valid checks show that it's valid
+        attr = 'my_attr_value' # string
+        attr_type =  'S'
+        result = self.cf._check_attr_type(attr, attr_type)
+        self.assertTrue(result[0])
+
+        attr = np.int64(1)
+        attr_type =  'N'
+        self.assertTrue(self.cf._check_attr_type(attr, attr_type)[0])
+
+        attr = np.int64(45) # numpy int
+        attr_type = 'D'
+        variable = np.int64(45)
+        self.assertTrue(self.cf._check_attr_type(
+            attr, attr_type, variable)[0])
+
+        # check failures
+        attr = 'my_attr_value'
+        attr_type = 'N' # should be numeric
+        self.assertFalse(self.cf._check_attr_type(attr, attr_type)[0])
+
+        attr = np.int(64)
+        attr_type = 'S' # should be string
+        self.assertFalse(self.cf._check_attr_type(attr, attr_type)[0])
+
+        attr = np.int32(45)
+        attr_type = 'D' # should match
+        variable = np.int64(45) # mismatching numpy types
+        self.assertFalse(self.cf._check_attr_type(
+            attr, attr_type, variable)[0])
+
+    def test_check_grid_mapping_attr_condition(self):
+        """
+        Ensure the CF-1.7 implementation of check_grid_mapping_attr_condition()
+        works as expected.
+        """
+
+        # test good
+
+        att_name = 'horizontal_datum_name'
+        att = 'Monte Mario (Rome)'
+        res = self.cf.check_grid_mapping_attr_condition(att, att_name)
+        self.assertTrue(res[0])
+
+        att_name = 'prime_meridian_name'
+        att = 'Athens'
+        res = self.cf.check_grid_mapping_attr_condition(att, att_name)
+        self.assertTrue(res[0])
+
+        att_name = 'reference_ellipsoid_name'
+        att = 'Airy 1830'
+        res = self.cf.check_grid_mapping_attr_condition(att, att_name)
+        self.assertTrue(res[0])
+
+        att_name = 'towgs84'
+        att = np.array([0, 0, 0], dtype=np.float64) # len 3
+        res = self.cf.check_grid_mapping_attr_condition(att, att_name)
+        self.assertTrue(res[0])
+        
+        att_name = 'towgs84'
+        att = np.array([0, 0, 0, 0, 0, 0], dtype=np.float64) # len 6
+        res = self.cf.check_grid_mapping_attr_condition(att, att_name)
+        self.assertTrue(res[0])
+
+        att_name = 'towgs84'
+        att = np.array([0, 0, 0, 0, 0, 0, 0], dtype=np.float64) # len 7
+        res = self.cf.check_grid_mapping_attr_condition(att, att_name)
+        self.assertTrue(res[0])
+
+        att_name = 'geographic_crs_name'
+        att = 'NAD83(CSRS98)'
+        res = self.cf.check_grid_mapping_attr_condition(att, att_name)
+        self.assertTrue(res[0])
+
+        att_name = 'geoid_name'
+        att = 'Mayotte 1950'
+        res = self.cf.check_grid_mapping_attr_condition(att, att_name)
+        self.assertTrue(res[0])
+
+        att_name = 'geopotential_datum_name'
+        att = 'NAVD88'
+        res = self.cf.check_grid_mapping_attr_condition(att, att_name)
+        self.assertTrue(res[0])
+
+        att_name = 'projected_crs_name'
+        att = 'Anguilla 1957 / British West Indies Grid'
+        res = self.cf.check_grid_mapping_attr_condition(att, att_name)
+        self.assertTrue(res[0])
+
+        # test bad
+
+        att_name = 'horizontal_datum_name'
+        att = 'bad'
+        res = self.cf.check_grid_mapping_attr_condition(att, att_name)
+        self.assertFalse(res[0])
+
+        att_name = 'prime_meridian_name'
+        att = 'bad'
+        res = self.cf.check_grid_mapping_attr_condition(att, att_name)
+        self.assertFalse(res[0])
+
+        att_name = 'reference_ellipsoid_name'
+        att = 'goofy goober'
+        res = self.cf.check_grid_mapping_attr_condition(att, att_name)
+        self.assertFalse(res[0])
+
+        att_name = 'towgs84'
+        att = np.array([0, 0, 0], dtype=np.int64) # len 3, wrong dtype
+        res = self.cf.check_grid_mapping_attr_condition(att, att_name)
+        self.assertFalse(res[0])
+
+        att_name = 'towgs84'
+        att = np.array([0, 0, 0, 0], dtype=np.int64) # len 4
+        res = self.cf.check_grid_mapping_attr_condition(att, att_name)
+        self.assertFalse(res[0])
+
+        att_name = 'towgs84'
+        att = np.float64(0) # single value, right dtype
+        res = self.cf.check_grid_mapping_attr_condition(att, att_name)
+        self.assertFalse(res[0])
+
+        att_name = 'geographic_crs_name'
+        att = 'badbadbadbadbadnotinhere'
+        res = self.cf.check_grid_mapping_attr_condition(att, att_name)
+        self.assertFalse(res[0])
+
+        att_name = 'geoid_name'
+        att = 'yooooooo'
+        res = self.cf.check_grid_mapping_attr_condition(att, att_name)
+        self.assertFalse(res[0])
+
+        att_name = 'geopotential_datum_name'
+        att = 'NAVBAD BAD'
+        res = self.cf.check_grid_mapping_attr_condition(att, att_name)
+        self.assertFalse(res[0])
+
+        att_name = 'projected_crs_name'
+        att = 'Teddy Bruschi'
+        res = self.cf.check_grid_mapping_attr_condition(att, att_name)
+        self.assertFalse(res[0])
+
+
+    def test_check_gmattr_existence_condition_geoid_name_geoptl_datum_name(self):
+        # create mock dataset for test; create three variables, one as dimensionless
+
+        # test good (either-or)
+        dataset = MockTimeSeries()
+        dataset.createVariable('lev', 'd') # dtype=double, dims=1
+        dataset.variables['lev'].setncattr('geoid_name', 'blah')
+        res = self.cf._check_gmattr_existence_condition_geoid_name_geoptl_datum_name(dataset.variables['lev'])
+        self.assertTrue(res[0])
+        dataset.close()
+
+        dataset = MockTimeSeries()
+        dataset.createVariable('lev', 'd') # dtype=double, dims=1
+        dataset.variables['lev'].setncattr('geopotential_datum_name', 'blah')
+        res = self.cf._check_gmattr_existence_condition_geoid_name_geoptl_datum_name(dataset.variables['lev'])
+        self.assertTrue(res[0])
+        dataset.close()
+
+        # bad
+        dataset = MockTimeSeries()
+        dataset.createVariable('lev', 'd') # dtype=double, dims=1
+        dataset.variables['lev'].setncattr('geopotential_datum_name', 'blah')
+        dataset.variables['lev'].setncattr('geoid_name', 'blah')
+        res = self.cf._check_gmattr_existence_condition_geoid_name_geoptl_datum_name(dataset.variables['lev'])
+        self.assertFalse(res[0])
+        dataset.close()
+
+    def test_check_gmattr_existence_condition_ell_pmerid_hdatum(self):
+
+        # test good (all)
+        dataset = MockTimeSeries()
+        dataset.createVariable('lev', 'd') # dtype=double, dims=1
+        dataset.variables['lev'].setncattr('reference_ellipsoid_name', 'blah')
+        dataset.variables['lev'].setncattr('prime_meridian_name', 'blah')
+        dataset.variables['lev'].setncattr('horizontal_datum_name', 'blah')
+        res = self.cf._check_gmattr_existence_condition_ell_pmerid_hdatum(dataset.variables['lev'])
+        self.assertTrue(res[0])
+        dataset.close()
+
+        # test bad (not all)
+        dataset = MockTimeSeries()
+        dataset.createVariable('lev', 'd') # dtype=double, dims=1
+        dataset.variables['lev'].setncattr('reference_ellipsoid_name', 'blah')
+        res = self.cf._check_gmattr_existence_condition_ell_pmerid_hdatum(dataset.variables['lev'])
+        self.assertFalse(res[0])
+        dataset.close()
+
+        # test bad (not all)
+        dataset = MockTimeSeries()
+        dataset.createVariable('lev', 'd') # dtype=double, dims=1
+        dataset.variables['lev'].setncattr('reference_ellipsoid_name', 'blah')
+        dataset.variables['lev'].setncattr('prime_meridian_name', 'blah')
+        res = self.cf._check_gmattr_existence_condition_ell_pmerid_hdatum(dataset.variables['lev'])
+        self.assertFalse(res[0])
+        dataset.close()