diff --git a/_toc.yml b/_toc.yml index 0b8c0d7..cc394e2 100644 --- a/_toc.yml +++ b/_toc.yml @@ -140,6 +140,12 @@ parts: - caption: GENERAL chapters: + # National Park Service + - file: src/overview/nps + sections: + - file: src/python/nps + title: "...in Python" + # Speedrun.com - file: src/overview/speedrun sections: diff --git a/src/overview/nps.rst b/src/overview/nps.rst new file mode 100644 index 0000000..3eae0b0 --- /dev/null +++ b/src/overview/nps.rst @@ -0,0 +1,21 @@ +National Park Service +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +.. sectionauthor:: Michael T. Moen + +Brief Overview +**************** + +The National Park Service (NPS) API contains pertinent information about national parks, monuments, and other sites managed by the NPS. An API key is required for this API, and registration can be found on the NPS website [#nps1]_ . Users are required to follow the rate limits of 1000 requests per hour [#nps2]_ . + +See the NPS API documentation [#nps3]_ for more information on accessing the API. Note that the data in the API is "is generally considered in the public domain," according to the API's disclaimer [#nps4]_ . + +.. rubric:: References + +.. [#nps1] ``_ + +.. [#nps2] ``_ + +.. [#nps3] ``_ + +.. [#nps4] ``_ diff --git a/src/python/nps.ipynb b/src/python/nps.ipynb new file mode 100644 index 0000000..215383e --- /dev/null +++ b/src/python/nps.ipynb @@ -0,0 +1,629 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# National Park Service API in Python\n", + "\n", + "by Nathaniel Pedigo and Michael T. Moen\n", + "\n", + "**NPS API documentation:** [https://www.nps.gov/subjects/developer/api-documentation.htm](https://www.nps.gov/subjects/developer/api-documentation.htm)\n", + "\n", + "**NPS API terms of use and disclaimer:** [https://www.nps.gov/aboutus/disclaimer.htm](https://www.nps.gov/aboutus/disclaimer.htm)\n", + "\n", + "- \"All sample data and information is attributed to National Park Service via the API service, no protection is claimed in original U.S. Government works.\"\n", + "\n", + "This API is managed by the National Park Service (NPS) of the United States of America. It contains pertinent information about the national parks, monuments, and other sites managed by the NPS. This API is free to use with an API key.\n", + "\n", + "*These recipe examples were tested on October 18, 2024.*\n", + "\n", + "**_NOTE:_** The National Park Service API limits requests to a maximum of 1000 requests per hour: https://www.nps.gov/subjects/developer/guides.htm" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### API Key\n", + "\n", + "An API key is required to access the National Park Service API. Sign up can be found here: [https://www.nps.gov/subjects/developer/get-started.htm](https://www.nps.gov/subjects/developer/get-started.htm)\n", + "\n", + "We start by importing our API key below:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "from api_key import key" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Import Libraries\n", + "\n", + "First step is to import the necessary libraries. We will be using the `requests` library to make the API calls, the `matplotlib` library to create data visualizations, and the 'numpy' library to assist in the creation of the data visualizations. If you are more familiar with Python and would prefer a different method of data visualization, feel free to use that instead. Additionally, as the API returns data in JSON format, we will be using the `json` library to parse the data." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "import requests\n", + "import matplotlib.pyplot as plt\n", + "from time import sleep\n", + "from pprint import pprint" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Find the NPS Parks in a State\n", + "\n", + "Count the number of National Park Service parks in Alabama.\n", + "\n", + "We use the following parameters in our API query:\n", + "- `stateCode` specifies the two letter code of the state being used as a filter. Note that the codes for U.S. territories can also be used (e.g. `DC` for the District of Columbia and `MP` for the Northern Mariana Islands).\n", + "- `limit` specifies the maximum number of results to return in a request. It is set to 50 by default.\n", + "\n", + "The NPS API allows for a number of parameters to be specified, including the state, the type of site, and the number of results returned. For this example, we will be using the state of Alabama, the type of site as national parks, and the maximum number of results returned as 200. " + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "metadata": {}, + "outputs": [], + "source": [ + "state = \"AL\"\n", + "limit = 200\n", + "\n", + "endpoint = \"parks\"\n", + "params = {\n", + " \"stateCode\": state,\n", + " \"limit\": limit,\n", + " \"api_key\": key\n", + "}\n", + "\n", + "url = f\"https://developer.nps.gov/api/v1/{endpoint}\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we use `requests.get()` to make the API call:" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "metadata": {}, + "outputs": [], + "source": [ + "response = requests.get(url, params=params)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, we must check that the request was performed successfully. The `status_code` field of the response can give us insight into any errors that may occur. The request returns a JSON object, which we can parse using the `json()` function." + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'data': [...], 'limit': '200', 'start': '0', 'total': '11'}\n" + ] + } + ], + "source": [ + "if response.status_code == 200:\n", + " # Parse the JSON response\n", + " data = response.json()\n", + "\n", + " # Print the first level of the JSON response\n", + " pprint(data, depth=1)\n", + "else:\n", + " # Print an error message if the request was not successful\n", + " print(f\"Error: {response.status_code}, {response.text}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, we can print our result:" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The number of NPS managed lands in AL is 11.\n" + ] + } + ], + "source": [ + "print(f\"The number of NPS managed lands in {state} is {data[\"total\"]}.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that none of the parks returned are National Parks, but other designations. See the [National Park Service website](https://www.nps.gov/aboutus/national-park-system.htm) to learn more about these designations." + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Park Name Designation\n", + "\n", + "Birmingham Civil Rights National Monument National Monument\n", + "Freedom Riders National Monument National Monument\n", + "Horseshoe Bend National Military Park National Military Park\n", + "Little River Canyon National Preserve National Preserve\n", + "Natchez Trace National Scenic Trail National Scenic Trail\n", + "Natchez Trace Parkway Parkway\n", + "Russell Cave National Monument National Monument\n", + "Selma To Montgomery National Historic Trail National Historic Trail\n", + "Trail Of Tears National Historic Trail National Historic Trail\n", + "Tuskegee Airmen National Historic Site National Historic Site\n", + "Tuskegee Institute National Historic Site National Historic Site\n" + ] + } + ], + "source": [ + "print(f\"{\"Park Name\":<45} Designation\\n\")\n", + "for park in data[\"data\"]:\n", + " print(f\"{park[\"fullName\"]:<45} {park[\"designation\"]}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Finding the Number of Parking Lots Created by NPS Entities by State\n", + "\n", + "In this example, we look at the number of \"parking lots created by national parks and other NPS entities\" by state using the `parkinglots` endpoint:" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'data': [...], 'limit': '200', 'start': '0', 'total': '17'}\n" + ] + } + ], + "source": [ + "# URL for Alabama\n", + "url = f\"https://developer.nps.gov/api/v1/parkinglots?stateCode=AL&limit=200&api_key={key}\"\n", + "response = requests.get(url)\n", + "\n", + "pprint(response.json(), depth=1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we'll loop through each state to find the total number of parking lots for each:" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": {}, + "outputs": [], + "source": [ + "# Create a list of all state abbreviations in the United States\n", + "states = [\"AL\", \"AK\", \"AZ\", \"AR\", \"CA\", \"CO\", \"CT\", \"DE\", \"FL\", \"GA\",\n", + " \"HI\", \"ID\", \"IL\", \"IN\", \"IA\", \"KS\", \"KY\", \"LA\", \"ME\", \"MD\",\n", + " \"MA\", \"MI\", \"MN\", \"MS\", \"MO\", \"MT\", \"NE\", \"NV\", \"NH\", \"NJ\",\n", + " \"NM\", \"NY\", \"NC\", \"ND\", \"OH\", \"OK\", \"OR\", \"PA\", \"RI\", \"SC\",\n", + " \"SD\", \"TN\", \"TX\", \"UT\", \"VT\", \"VA\", \"WA\", \"WV\", \"WI\", \"WY\"]\n", + "\n", + "# Create an empty dictionary to store the total number of parking lots per state\n", + "parking_lots = {}\n", + "\n", + "for state in states:\n", + "\n", + " url = f\"https://developer.nps.gov/api/v1/parkinglots?stateCode={state}&limit=200&api_key={key}\"\n", + " response = requests.get(url)\n", + "\n", + " # Extract the total number of NPS parking lots in the state\n", + " total = int(response.json()[\"total\"])\n", + "\n", + " # Add the total to the parking_lots list\n", + " parking_lots[state] = total\n", + "\n", + " # Delay for some period between between API calls\n", + " sleep(0.75)" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": {}, + "outputs": [], + "source": [ + "# Create a list of the top 5 states with the most parking lots using\n", + "states_sorted = sorted(parking_lots.items(), key=lambda item: item[1], reverse=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Top 5 States by NPS Parking Lots:\n", + "CA: 59\n", + "GA: 50\n", + "AZ: 43\n", + "VA: 42\n", + "FL: 34\n" + ] + } + ], + "source": [ + "print(\"Top 5 States by NPS Parking Lots:\")\n", + "for state in states_sorted[:5]:\n", + " print(f\"{state[0]}: {state[1]}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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kGS8++67dtM7depkWCwW48SJE7ZpefPmTXadBQoUSHUdKQkJCTEkGW3atLGb/uqrrxqSjL179xqGYRjXr183cuXKZYwcOdJuvuDgYCNv3rzGrVu3Ul2Po/vTggULDEnGxo0bk7yW3P7Sr18/I0+ePMadO3ds01q1amWUKlUqybxz5swxsmXLZvz6669207/44gtDkrF169ZUtwF4EBweAu5RpkwZ9erVS1999ZUuXLiQ6rxr165V0aJFVbRoUVWrVk0LFixQr169NGHCBNs83377raZPn67SpUtryZIlGj58uCpWrKjGjRsne6ZScmbMmKGiRYuqWLFiqlmzpu1QzODBgyVJuXPnts0bHR2tK1euqE6dOjIMQ3v27EnS3iuvvJLiuubNm6euXbuqX79++vLLL5Utm2M/ES+//LIsFovted26dRUfH6+zZ89KktavX6+4uDi9+uqrdsu99tprDrXviFWrVsnNzU3BwcF204cNGybDMBwauFywYEH9/vvvioiISFcNAwYMsHueuH2rVq2SJBUoUEBt27bVvHnzbIes4uPjFRoaqnbt2qU5vsgV+9O9+8vNmzd15coV1a1bVzExMTpy5Eiayy9YsEAVK1ZUhQoVdOXKFdujUaNGkqSNGzc6VAeQHoQW4D7//e9/FRcXl+bYlpo1a2rdunX6+eeftW3bNl25ckWzZ8+2+6OQLVs2DRgwQLt27dKVK1e0bNkytWjRQhs2bFC3bt0cqqdt27a29fz++++6cuWKJk+ebAsUf/75p/r06aNChQrJ3d1dRYsWVf369SVJN27csGsre/bsKlmyZLLrOX36tHr27KmOHTvqk08+sQshaXn88cftnnt4eEj651CWJFt4STyklahQoUK2eR/U2bNn5enpqXz58tlNTzwsklhDaiZOnKgDBw7I29tbTz75pMaMGaNTp045XEPZsmXtnvv6+ipbtmx2Z/H07t1bf/75p3799VdJ0s8//6y//vpLvXr1SrN9V+xPBw8eVPv27VWgQAHlz59fRYsWtQ30vn9/Sc7x48d18OBBW2BPfJQrV06SdOnSJYfqANKDMS3AfcqUKaOePXvqq6++0qhRo1Kcr0iRImrSpInD7RYuXFht2rRRmzZt1KBBA23atElnz561jVVIScmSJVNcT3x8vJ555hldu3ZNI0eOVIUKFZQ3b16Fh4erT58+SkhIsJvfarWm2HtSokQJlShRQqtWrdLOnTtVo0YNh7ctpTEvxj0DYM2gS5cutuvtrF27VpMmTdKECRO0ePHidJ2plVzwa9asmR577DF99913qlevnr777jsVL17cqX1JSt/+dP36ddWvX1/58+fXuHHj5Ovrq1y5cmn37t0aOXJkkv0lOQkJCapSpYo++uijZF/39vZ2ajsAZ9DTAiQjsbfl3kM9rpQYCNI6BJWW/fv369ixY5o8ebJGjhyptm3bqkmTJk4PIpakXLlyaeXKlSpbtqyaN2+ugwcPPlBt90r8Q3rixAm76VevXrX1xrhiHREREbp586bd9MRDHvf+MU+tF6lEiRJ69dVXtXTpUp0+fVqFCxfWe++951ANx48ft3t+4sQJJSQk2J154+bmph49emjhwoWKjIzU0qVL1b179zQHO6fm/v0ppe375ZdfdPXqVc2cOVODBg3Ss88+qyZNmiTb25VSG76+vrp27ZoaN26sJk2aJHmUL18+3dsBpIXQAiTD19dXPXv21JdffqmLFy+mq42LFy/q0KFDSabfvXtX69evV7Zs2ZIcLnFW4h+6e3s0DMPQ1KlT09VegQIFtGbNGtupvidPnnyg+hI1btxY2bNn1+eff243ffr06S5pX5Jatmyp+Pj4JG1OmTJFFovFrqckb968un79ut188fHxSQ6PFCtWTJ6eng6fyvvpp5/aPf/kk08kKUkvTa9evRQZGal+/frp1q1bdtfhSYkz+1Pi2Jj7tzG5/eXu3bv67LPPkrSbN2/eZA8XdenSReHh4cleZPD27duKjo5Oc1uA9OLwEJCCN998U3PmzNHRo0dVuXJlp5c/f/68nnzySTVq1EiNGzdW8eLFdenSJc2bN0979+7V4MGDVaRIkQeqsUKFCvL19dXw4cMVHh6u/Pnza9GiRQ/Ue1GkSBGtW7dOTz/9tJo0aaItW7bIy8vrgep87LHHNGjQIE2ePFlt2rRR8+bNtXfvXq1evVpFihRxePzMzp079e677yaZ3qBBA7Vu3VoNGzbUm2++qTNnzqhatWpau3atli1bpsGDB9tOa5akoKAg/fzzz/roo4/k6emp0qVLq3z58ipZsqQ6deqkatWqyd3dXT///LP++OMPTZ482aH6Tp8+bdu+3377Td9995169OiR5Nos1atXl7+/v21QqyOnUzuzPwUEBMjN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Gw0aNNDmzZvtpr322muaPn16kvYdkT17drsabt++rZkzZ2r48OFpLmuxWPTqq6/anl+4cEFubm4aM2aMJGnMmDEqWbKkAgICVLZsWXXv3l1nz55N0s6QIUP0xRdf2J77+/vrgw8+sD0vWbKkLBaLoqOjbdPq1aun9evXp7ld/v7+6ty5s2JiYiRJMTExcnd316effprm9t29e9f2vhQvXty2LRaLRTVq1FBCQoIk6ddff1XNmjVTvKz1mTNnVKNGDbtpffr0sdX4+OOPq1ixYrZ1Xb9+PdW67v+cHf28EqX1uR0+fFj16tVTQECAKlSooJCQkFTbK1KkiG078+TJo+rVq6t8+fKqU6eOFi9e/MD1JHLku5ZaW9OmTbO9x/fu97Nnz07Szp9//qlWrVqpbNmy8vPzU0hIiAzDSPa9Ttz++6W1X3t5eSW5uObff/+t4sWL69q1a8m2ef+6hg8frpkzZ0r6Z59auXJlssul1taZM2dksVj0v//9z/Zap06d9MsvvzjUjsVi0X//+98Hqiml91By/Pc6tToMw9B7772ncuXKqXr16qpdu7ZWrVqVbDspfe89PT2dep+c+X0MCAhQnTp1nN620NBQtW/f3jb9ypUr8vPz05UrV1Js62EhtDjBMAy1bdtWrVq10smTJ7Vz504FBwfr8uXLunv3rjZu3Kg7d+7o1KlTabbTrl07tWrVSqdOndKuXbv04Ycf2pbbsWOHihYtqsWLF6d5HwZJCg0N1RNPPKElS5ZIktasWaOwsDCFhYVp3bp1yp07t958881U2+jatat++OEH2/OEhAQtWbJEnTp1StK+IwoWLGirISwsTLlz53Z42UKFCmn79u2Kj//n3hgLFy5U5cqV7eYZNWqUwsLCdPz4cT311FNq2LCh3X2oJKl27dravn27pH+u8mi1Wm3P//77b8XExGjMmDF69913JUnff/+9SpQoocaNG6e5XQcOHFDOnDltfzxWrlypatWqKTQ0NM3ty5kzp+196d+/v21bDMOQv7+/vvjiC8XHxys4OFiffPKJ01d3Xrp0qcLCwjRu3Dj17t3btq60LryYns/5Xml9boMGDdJbb72lsLAwHTx4MM0gfa9KlSppz549Onr0qL788ksNGzYszRupOrIfOfpdS62t4OBgu/c48d+9e/e2a8MwDLVv3149evTQ8ePHdeDAAe3evVvTpk1z+H2Q0t6vb9++rcDAQO3cudO2zLp16xQUFKRChQo5ta4HVbx4cX300Ue2IO4Md3d3zZ07Vzdv3nR5Xc78XqdWx7Rp07R161bt3r1be/bs0YoVK+z+J+heKX3vt23b5tT75MzvY2L7zm5b165dFRUVZfuOvfHGGxo2bFiqIfBhIbQ4Yf369XJ3d9eLL75om1a3bl35+/trzZo1qlevnrp3757mH66ff/5Z+fLl03/+8x/btICAAHXr1k3SP388+vXrJ19fX9uPUUquXbumY8eOaeLEicmut2/fvnrttddUtWrVVNvp2LGjli1bZvvSbN68WeXKlVPOnDlTbT8jWCwW1a1bV5s2bZIkLVmyRB06dEhx/oEDB8rb21urV6+2m167dm399ttvkqTff/9dLVu21KVLlyRJYWFhqlSpkkaNGqXly5crLCxM77zzjj766COH66xbt65OnDgh6Z/P7J133tGlS5dSvMGnIyZMmKBJkyZp3LhxCgoKSvEWFq6W1n7kiLQ+t4sXL9ou0e3m5qZKlSqlaz1VqlTR22+/rc8+++yB6pEc/645u08mJ/H347nnnpMk5cqVS9OmTdOkSZOcaseR/fq5557TggULbMv88MMPToVEV/Hy8lJgYKBdLY6yWq167rnn0vyc08OZ3+vU6pg0aZI+/fRTubu7S/qnZ6dz585O1+PM++SKfTFRats2ffp0DRs2TNu3b1dYWJj69euXrnW4GqHFCYcOHVJgYGCyr4WGhqpz587q2rVrml+Cw4cPKyAgINnXDMPQ0qVL1a5dO4faWrx4sdq2bas6dero+PHjdt13M2bMUGRkpIYNG5b6hkkqVqyYKlSooF9//VXS//+RS6391Fy/ft3WPfnSSy85tMy9unTpoh9++EERERHKmTNnmgk/MDBQR48etZvm7e2tmJgYXb16Vdu3b1etWrVUunRpnTp1Stu3b1edOnVktVo1adIk1atXT//5z3/s7jCemri4OK1evVpVqlTRzZs3tWvXLtWvX1+dO3dO1w90oscee0x9+/bVlClTNH78+HS346z0fs73S+1zCw4OVp06ddS6dWt99tlnun37drrrTe7zdrYeZ79rzu6T90vu96N06dKKjo5WVFSUZs+ebXdINaXDeY7s1126dNGiRYsk/dOr8NNPP6lt27Yp1nbv9zWlQ1vpNWrUKE2Y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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Extract keys and values for plotting\n", + "states_top25 = [state[0] for state in states_sorted[:25]]\n", + "values_top25 = [state[1] for state in states_sorted[:25]]\n", + "\n", + "# Create a bar chart\n", + "plt.bar(states_top25, values_top25, color='limegreen')\n", + "plt.title(\"NPS Parking Lots by State\")\n", + "plt.xlabel(\"State\")\n", + "plt.ylabel(\"Number of Parking Lots\")\n", + "plt.xticks(fontsize=7)\n", + "plt.yticks(fontsize=7)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Finding Campground Addresses\n", + "\n", + "Prompt the user for a state, then a selection of the parks in that state. Return the addresses of all campgrounds in the specified park." + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Available NPS managed lands in WY:\n", + "1. Bighorn Canyon National Recreation Area\n", + "2. California National Historic Trail\n", + "3. Devils Tower National Monument\n", + "4. Fort Laramie National Historic Site\n", + "5. Fossil Butte National Monument\n", + "6. Grand Teton National Park\n", + "7. Mormon Pioneer National Historic Trail\n", + "8. Oregon National Historic Trail\n", + "9. Pony Express National Historic Trail\n", + "10. Yellowstone National Park\n" + ] + } + ], + "source": [ + "state = \"\"\n", + "\n", + "# Prompt user for a state code using the states list from Section 2\n", + "while state not in states:\n", + " state = input(\"Enter a valid state code: \")\n", + "\n", + "# Perform the API call\n", + "endpoint = f\"https://developer.nps.gov/api/v1/parks?stateCode={state}&limit=150&api_key={key}\"\n", + "response = requests.get(endpoint)\n", + "\n", + "# Offer user a selection from available parks within specified state\n", + "if response.status_code == 200:\n", + "\n", + " parks = response.json()[\"data\"]\n", + " park_names = [park[\"fullName\"] for park in parks]\n", + " park_codes = [park[\"parkCode\"] for park in parks]\n", + "\n", + " print(f\"Available NPS managed lands in {state}:\")\n", + " for i, park_name in enumerate(park_names):\n", + " print(f\"{i+1}. {park_name}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "metadata": { + "tags": [ + "output_scroll" + ] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "There are 12 campgrounds in Yellowstone National Park.\n", + "\n", + "Name: Bridge Bay Campground\n", + "Address: GPS Coordinates: N 44 32.070 W 110 26.218 Yellowstone National Park, WY 82190\n", + "\n", + "Name: Canyon Campground\n", + "Address: GPS Coordinates: N 44 44.118 W110 29 17 Yellowstone National Park, WY 82190\n", + "\n", + "Name: Fishing Bridge RV Park\n", + "Address: GPS Coordinates: N 44 33.820 W 110 22.167 Yellowstone National Park, WY 82190\n", + "\n", + "Name: Grant Village Campground\n", + "Address: GPS Coordinates-- N 44 23.610 W 110 33.769 Yellowstone National Park, WY 82190\n", + "\n", + "Name: Indian Creek Campground\n", + "Address: GPS Coordinates--N: 44 53.22138 W: 110 44.16414 Yellowstone National Park, WY 82190\n", + "\n", + "Name: Lewis Lake Campground\n", + "Address: GPS Coordinates-- N 44.2822056, W -110.6279873 Yellowstone National Park, WY 82190\n", + "\n", + "Name: Madison Campground\n", + "Address: N 44 38.725 W 110 51.687 Yellowstone National Park, WY 82190\n", + "\n", + "Name: Mammoth Campground\n", + "Address: GPS Coordinates N 44 58.4166 W 110.41.59392 Yellowstone National Park, WY 82190\n", + "\n", + "Name: Norris Campground\n", + "Address: GPS Coordinates: N 44 44.27088 W 110 41.6169 Yellowstone National Park, WY 82190\n", + "\n", + "Name: Pebble Creek Campground\n", + "Address: GPS Coordinates -- N 44 55.01886 W 110 6.82848 Yellowstone National Park, WY 82190\n", + "\n", + "Name: Slough Creek Campground\n", + "Address: GPS Coordinates -- N: 44.9488466 W:-110.3068792 Yellowstone National Park, WY 82190\n", + "\n", + "Name: Tower Fall Campground\n", + "Address: GPS Coordinates N: 44 53.373 W 110 23.36856 Yellowstone National Park, WY 82190\n" + ] + } + ], + "source": [ + "# Prompt to pick a park\n", + "park_choice = 0\n", + "while (park_choice < 1) or (park_choice > len(park_names)):\n", + " park_choice = int(input(f\"Enter the number of the park you would like to explore (1-{len(park_names)}): \"))\n", + "\n", + "# Get the park code for the selected park\n", + "park_code = park_codes[park_choice-1]\n", + "park_name = park_names[park_choice-1]\n", + "\n", + "# Create the endpoint URL with the park code\n", + "endpoint = f\"https://developer.nps.gov/api/v1/campgrounds?parkCode={park_code}&api_key={key}\"\n", + "\n", + "# Perform the API call\n", + "response = requests.get(endpoint)\n", + "\n", + "# Extract the total number of campgrounds in the park\n", + "if response.status_code == 200:\n", + " data = response.json()\n", + " \n", + " campgrounds = data[\"data\"]\n", + " total = data[\"total\"]\n", + "\n", + " print(f\"There are {total} campgrounds in {park_name}.\")\n", + "\n", + " # Print the names and addresses of the campgrounds\n", + " if int(total) > 0:\n", + " for campground in campgrounds:\n", + " print(f\"\\nName: {campground['name']}\")\n", + " \n", + " for address in campground[\"addresses\"]:\n", + " if address[\"type\"] == \"Physical\":\n", + " print(f\"Address: {address['line1']} {address['city']}, {address['stateCode']} {address['postalCode']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Finding Locations with Restrooms within a Park\n", + "\n", + "Prompt the user for a state, then a selection of the parks in that state. Return the names of all locations in the park that have restrooms listed as an amenity." + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Available NPS managed lands in the specified state:\n", + "1. Butterfield Overland National Historic Trail\n", + "2. California National Historic Trail\n", + "3. Gateway Arch National Park\n", + "4. George Washington Carver National Monument\n", + "5. Harry S Truman National Historic Site\n", + "6. Lewis & Clark National Historic Trail\n", + "7. Oregon National Historic Trail\n", + "8. Ozark National Scenic Riverways\n", + "9. Pony Express National Historic Trail\n", + "10. Santa Fe National Historic Trail\n", + "11. Ste. Geneviève National Historical Park\n", + "12. Trail Of Tears National Historic Trail\n", + "13. Ulysses S Grant National Historic Site\n", + "14. Wilson's Creek National Battlefield\n" + ] + } + ], + "source": [ + "state = \"\"\n", + "\n", + "# Prompt user for a state code\n", + "while state not in states:\n", + " state = input(\"Enter a valid state code: \")\n", + "\n", + "# Create the endpoint URL with the state code\n", + "endpoint = f\"https://developer.nps.gov/api/v1/parks?stateCode={state}&limit=50&api_key={key}\"\n", + "\n", + "# Perform the API call\n", + "response = requests.get(endpoint)\n", + "\n", + "# Offer user a selection from available parks within specified state\n", + "if response.status_code == 200:\n", + " data = response.json()\n", + " parks = data[\"data\"]\n", + " park_names = [park[\"fullName\"] for park in parks]\n", + " park_codes = [park[\"parkCode\"] for park in parks]\n", + " print(\"Parks in the specified state:\")\n", + " for i, park_name in enumerate(park_names):\n", + " print(f\"{i+1}. {park_name}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The following locations in Ulysses S Grant National Historic Site have restrooms:\n", + "\n", + "Grant's Farm\n", + "Jefferson Barracks\n", + "Thomas Sappington House Museum\n", + "White Haven\n" + ] + } + ], + "source": [ + "# Prompt to pick a park\n", + "park_choice = 0\n", + "while (park_choice < 1) or (park_choice > len(park_names)):\n", + " park_choice = int(input(f\"Enter the number of the park you would like to explore (1-{len(park_names)}): \"))\n", + "\n", + "# Get the park code for the selected park\n", + "park_code = park_codes[park_choice-1]\n", + "park_name = park_names[park_choice-1]\n", + "\n", + "# Create the endpoint URL with the park code\n", + "endpoint = f\"https://developer.nps.gov/api/v1/places?parkCode={park_code}&api_key={key}\"\n", + "\n", + "# Perform the API call\n", + "response = requests.get(endpoint)\n", + "\n", + "# Extract all locations in the park\n", + "data = response.json()\n", + "\n", + "if(data[\"total\"] == 0):\n", + " print(f\"There are no locations in {park_name}.\")\n", + "else:\n", + " cell_locations = []\n", + "\n", + " for location in data[\"data\"]:\n", + " if \"Restroom\" in location[\"amenities\"]:\n", + " cell_locations.append(location[\"title\"])\n", + "\n", + " if cell_locations:\n", + " print(f\"The following locations in {park_name} have restrooms:\\n\")\n", + " for location in cell_locations:\n", + " print(location)\n", + " else:\n", + " print(f\"There are no locations in {park_name} with restrooms.\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "cookbook-env", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.1" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/src/python/speedrun.ipynb b/src/python/speedrun.ipynb index d314ca7..e457c16 100644 --- a/src/python/speedrun.ipynb +++ b/src/python/speedrun.ipynb @@ -585,7 +585,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 3. Graphing an Interactive World Record Progression Graph\n", + "## 3. Graphing a World Record Progression Graph\n", "\n", "This code fetches data from the Speedrun\\.com API for Mario Kart Wii's 32 Tracks (No Skips) category, accumulates the dates and runtimes of verified runs, and identifies the progression of the world record over time. It then sorts the data and extracts the relevant dates and runtimes to graph the progression of this world record category.\n", "\n",