diff --git a/environment.yml b/environment.yml index d757600f..cd286364 100644 --- a/environment.yml +++ b/environment.yml @@ -3,6 +3,7 @@ channels: - conda-forge dependencies: # Content + - astropy - cartopy - geocat-comp - geocat-datafiles diff --git a/ncl/ncl_entries/great_circle.ipynb b/ncl/ncl_entries/great_circle.ipynb index 42d4cbf1..b3c57426 100644 --- a/ncl/ncl_entries/great_circle.ipynb +++ b/ncl/ncl_entries/great_circle.ipynb @@ -15,7 +15,8 @@ "\n", "This section covers great circle functions from NCL:\n", "\n", - "- [area_poly_sphere](https://www.ncl.ucar.edu/Document/Functions/Built-in/area_poly_sphere.shtml)" + "- [area_poly_sphere](https://www.ncl.ucar.edu/Document/Functions/Built-in/area_poly_sphere.shtml)\n", + "- [css2c](https://www.ncl.ucar.edu/Document/Functions/Built-in/css2c.shtml)" ] }, { @@ -23,7 +24,7 @@ "metadata": {}, "source": [ "## area_poly_sphere\n", - "NCL's `area_poly_sphere` calculates the area enclosed by an arbitrary polygon on the sphere\n", + "NCL's `area_poly_sphere` calculates the area enclosed by an arbitrary polygon on the sphere\n", "\n", "
\n", "

Important Note

\n", @@ -58,6 +59,40 @@ "poly_area_km2" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## css2c\n", + "NCL's `css2c` converts spherical (latitude/longitude) coordinates to Cartesian coordinates on a unit sphere" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Grab and Go" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from astropy.coordinates.representation import UnitSphericalRepresentation\n", + "from astropy import units\n", + "\n", + "lat = 40.0150\n", + "lon = -105.2705\n", + "\n", + "spherical_coords = UnitSphericalRepresentation(lat=lat * units.deg, lon=lon * units.deg)\n", + "cart_coords = spherical_coords.to_cartesian()\n", + "print(f\"X = {cart_coords.x.value}\")\n", + "print(f\"Y = {cart_coords.y.value}\")\n", + "print(f\"Z = {cart_coords.z.value}\")" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -71,6 +106,7 @@ "source": [ "## Python Resources\n", "- [pyroj.geod() great circle computations](https://pyproj4.github.io/pyproj/stable/api/geod.html)\n", + "- [Astropy Coordinate Systems](https://docs.astropy.org/en/stable/coordinates/representations.html)\n", "\n", "## Additional Reading\n", "- [Aviation Formulary for working with great circles](https://www.edwilliams.org/avform147.htm)" diff --git a/ncl/ncl_index/ncl-index-table.csv b/ncl/ncl_index/ncl-index-table.csv index c339c5e7..64f1a8ca 100644 --- a/ncl/ncl_index/ncl-index-table.csv +++ b/ncl/ncl_index/ncl-index-table.csv @@ -46,4 +46,5 @@ NCL Function,Description,Python Equivalent,Notes `daylight_fao56 `__," Compute maximum number of daylight hours as described in FAO 56","``geocat.comp.meteorology.max_daylight()``",`example notebook <../ncl_entries/meteorology.ipynb#daylight-fao56>`__ `dewtemp_trh `__,"Calculates the dew point temperature given temperature and relative humidity","``geocat.comp.dewtemp()``",`example notebook <../ncl_entries/meteorology.ipynb#dewtemp-trh>`__ `area_poly_sphere `__,"Calculates the area enclosed by an arbitrary polygon on the sphere","``pyproj.Geod()`` and ``shapely.geometry.polygon.Polygon()``",`example notebook <../ncl_entries/great_circle.ipynb#area-poly-sphere>`__ +`css2c `__,"Converts spherical coordinates (lat/lon) to Cartesian coordinates on a unit sphere","``astropy.UnitSphericalRepresentation()``",`example notebook <../ncl_entries/great_circle.ipynb#css2c>`__ `satvpr_temp_fao56 `__," Compute saturation vapor pressure using temperature as described in FAO 56","``geocat.comp.saturation_vapor_pressure()``",`example notebook <../ncl_entries/meteorology.ipynb#satvpr-temp-fao56>`__ diff --git a/ncl/ncl_raw/great_circle.ncl b/ncl/ncl_raw/great_circle.ncl index 3097e798..b3093f69 100644 --- a/ncl/ncl_raw/great_circle.ncl +++ b/ncl/ncl_raw/great_circle.ncl @@ -68,3 +68,18 @@ print(gc_clkwise(lat7, lon7)) ; (0) True print(area_poly_sphere(lat7, lon7, 6370.997)) ;(0) 9401.705 + +; css2c +; Adapted from https://www.ncl.ucar.edu/Document/Functions/Built-in/css2c.shtml + +; ncl -n css2c.ncl >> css2c_output.txt + +print("Latitude (Degree), Longitude (Degree), Cartesian X, Cartesian Y, Cartesian Z") +do lat=-90,90 + do lon=-180,180 + begin + cart = css2c(lat, lon) + print (lat + "," + lon + "," + cart(0,0) + "," + cart(1,0) + "," + cart(2,0)) + end + end do +end do diff --git a/ncl/receipts/great_circle.ipynb b/ncl/receipts/great_circle.ipynb index a89522a8..68300d1c 100644 --- a/ncl/receipts/great_circle.ipynb +++ b/ncl/receipts/great_circle.ipynb @@ -24,7 +24,8 @@ "metadata": {}, "source": [ "## Functions covered\n", - "- [area_poly_sphere](https://www.ncl.ucar.edu/Document/Functions/Built-in/area_poly_sphere.shtml)" + "- [area_poly_sphere](https://www.ncl.ucar.edu/Document/Functions/Built-in/area_poly_sphere.shtml)\n", + "- [css2c](https://www.ncl.ucar.edu/Document/Functions/Built-in/css2c.shtml)" ] }, { @@ -53,10 +54,18 @@ "## Python Functionality" ] }, + { + "cell_type": "markdown", + "id": "6dc1d6c4-ac26-4346-9aa3-2c73182a7511", + "metadata": {}, + "source": [ + "### area_poly_sphere" + ] + }, { "cell_type": "code", "execution_count": null, - "id": "95225429-62d5-4d38-b170-850526c28107", + "id": "2ba99b37-a743-4776-bc7b-d5a08b977642", "metadata": {}, "outputs": [], "source": [ @@ -73,24 +82,8 @@ " \"Half of the World\",\n", " \"Single Point -> Invalid NCL\",\n", " \"Single Degree\",\n", - "]" - ] - }, - { - "cell_type": "markdown", - "id": "6dc1d6c4-ac26-4346-9aa3-2c73182a7511", - "metadata": {}, - "source": [ - "### area_poly_sphere" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2ba99b37-a743-4776-bc7b-d5a08b977642", - "metadata": {}, - "outputs": [], - "source": [ + "]\n", + "\n", "ncl_lat = [\n", " [40.0150, 42.3601, 29.5518],\n", " [41.00488, 41.00203, 37.00540, 37.00051],\n", @@ -118,24 +111,68 @@ "ncl_results[poly_name[4]] = 54450.39\n", "ncl_results[poly_name[5]] = 255032000\n", "ncl_results[poly_name[6]] = -127516000\n", - "ncl_results[poly_name[7]] = 9401.705" + "ncl_results[poly_name[7]] = 9401.705\n", + "\n", + "from pyproj import Geod\n", + "\n", + "python_results = {}\n", + "geod = Geod(ellps=\"sphere\") # radius = 6370997 m\n", + "for i in range(len(poly_name)):\n", + " poly_area_m, _ = geod.polygon_area_perimeter(ncl_lon[i], ncl_lat[i])\n", + " poly_area_km2 = abs(poly_area_m) * 1e-6\n", + " python_results[poly_name[i]] = poly_area_km2" + ] + }, + { + "cell_type": "markdown", + "id": "c0e65c3e-2377-47ec-b94c-e7eb753966d9", + "metadata": {}, + "source": [ + "### css2c" ] }, { "cell_type": "code", "execution_count": null, - "id": "35803c5d-bf83-4a35-b61c-50466d9d5095", + "id": "35abde81-5843-4504-8e32-a137ee1aa094", "metadata": {}, "outputs": [], "source": [ - "from pyproj import Geod\n", + "import geocat.datafiles as gdf\n", + "import numpy as np\n", + "from astropy.coordinates.representation import UnitSphericalRepresentation\n", + "from astropy import units\n", "\n", - "python_results = {}\n", - "geod = Geod(ellps=\"sphere\") # radius = 6370997 m\n", - "for i in range(len(poly_name)):\n", - " poly_area_m, _ = geod.polygon_area_perimeter(ncl_lon[i], ncl_lat[i])\n", - " poly_area_km2 = abs(poly_area_m) * 1e-6\n", - " python_results[poly_name[i]] = poly_area_km2" + "css2c_data = gdf.get('applications_files/ncl_outputs/css2c_output.txt')\n", + "css2c_data = np.loadtxt(css2c_data, delimiter=',', skiprows=6)\n", + "\n", + "lat_lon = tuple(map(tuple, (css2c_data[::, 0:2])))\n", + "cart_values = css2c_data[::, 2:]\n", + "ncl_css2c = dict(zip(lat_lon, cart_values))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ee104ea1-e287-4635-b404-5b06ccfb6949", + "metadata": {}, + "outputs": [], + "source": [ + "## Collect Latitude and Longtiude Pairs\n", + "lat_lon = []\n", + "for lat in range(-90, 90 + 1):\n", + " for lon in range(-180, 180 + 1):\n", + " lat_lon.append((lat, lon))\n", + "\n", + "## Calculate Cartesian coordinates\n", + "astropy_css2c = {}\n", + "for i, pair in enumerate(lat_lon):\n", + " lat, lon = pair\n", + " spherical_coords = UnitSphericalRepresentation(\n", + " lat=lat * units.deg, lon=lon * units.deg\n", + " )\n", + " cart_coords = spherical_coords.to_cartesian()\n", + " astropy_css2c[pair] = cart_coords.xyz.value" ] }, { @@ -146,6 +183,14 @@ "## Comparison" ] }, + { + "cell_type": "markdown", + "id": "384790de-53f9-45d7-8bc1-fef86df21b57", + "metadata": {}, + "source": [ + "### area_poly_sphere" + ] + }, { "cell_type": "markdown", "id": "cb61d4f0-bd10-4034-bfe8-b0c6e9137ec3", @@ -180,6 +225,28 @@ " if key != \"Single Point -> Invalid NCL\":\n", " assert False" ] + }, + { + "cell_type": "markdown", + "id": "57251c09-ef8c-438f-b3cf-cf798e7f4028", + "metadata": {}, + "source": [ + "### css2c" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d5738d2d-5abe-4ed4-8e90-c42881c2bfb0", + "metadata": {}, + "outputs": [], + "source": [ + "for key in ncl_css2c.keys():\n", + " if key in astropy_css2c.keys():\n", + " assert abs(ncl_css2c[key][0] - astropy_css2c[key][0]) < 0.000005\n", + " assert abs(ncl_css2c[key][1] - astropy_css2c[key][1]) < 0.000005\n", + " assert abs(ncl_css2c[key][2] - astropy_css2c[key][2]) < 0.000005" + ] } ], "metadata": {