Skip to content

Commit

Permalink
Convert to library (#6)
Browse files Browse the repository at this point in the history
- Move main functionality from notebook to small library
- Add CLI for simple invokation of the library
- Refactors notebook code to use library from pip
- Add github workflow for pip deployment
  • Loading branch information
GalenReich authored May 13, 2024
1 parent 4d13322 commit 813be09
Show file tree
Hide file tree
Showing 11 changed files with 1,093 additions and 194 deletions.
17 changes: 17 additions & 0 deletions .github/workflows/pypi-publish.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: Upload Python Package

on:
release:
types: [published]

jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and publish to pypi
uses: JRubics/[email protected]
with:
pypi_token: ${{ secrets.PYPI_TOKEN }}
python_version: 3.9
17 changes: 16 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,16 @@
.venv/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Output files
*.png
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Shadow Finder
# ShadowFinder

A Jupyter Notebook tool to estimate the points on the Earth's surface where a shadow of a particular length could occur, for geolocation purposes.
A lightweight tool and Google Colab notebook for estimating the points on the Earth's surface where a shadow of a particular length could occur, for geolocation purposes.

Using an object's height, the lenth of its shadow, the date and the time, Shadow Finder estimates the possible locations where that shadow could occur.
Using an object's height, the lenth of its shadow, the date and the time, ShadowFinder estimates the possible locations where that shadow could occur.

[Try out ShadowFinder on Google Colab now](https://colab.research.google.com/github/GalenReich/ShadowFinder/blob/main/ShadowFinderColab.ipynb)
[Try out ShadowFinder on Google Colab now](https://colab.research.google.com/github/Bellingcat/ShadowFinder/blob/main/ShadowFinderColab.ipynb)
86 changes: 29 additions & 57 deletions ShadowFinderColab.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -24,81 +24,53 @@
"# @markdown ### ⬅️ Click to find possible locations that match the below information (takes around 10 - 20 seconds)\n",
"\n",
"# @markdown Object and shadow are measured at right angles in arbitrary units\n",
"object_height = 10 # @param {type:\"number\"} Height of object in arbitrary units\n",
"shadow_length = 8 # @param {type:\"number\"} Length of shadow in arbitrary units\n",
"object_height = 10 # @param {type:\"number\"} Height of object in arbitrary units\n",
"shadow_length = 8 # @param {type:\"number\"} Length of shadow in arbitrary units\n",
"\n",
"# @markdown Date and time must be given in UTC, using the time format hh:mm:ss\n",
"date = '2024-02-29' # @param {type:\"date\"}\n",
"time = '12:00:00' # @param {type:\"string\"}\n",
"date = \"2024-02-29\" # @param {type:\"date\"}\n",
"time = \"12:00:00\" # @param {type:\"string\"}\n",
"\n",
"#Create output files\n",
"# Create output files\n",
"output = f\"./shadowfinder_{object_height}_{shadow_length}_{date}T{time}.png\"\n",
"logfile = f\"./shadowfinder_{object_height}_{shadow_length}_{date}T{time}.log\"\n",
"\n",
"# Imports\n",
"![ ! -f \"deps_loaded\" ] & pip install suncalc basemap >> {logfile} 2>&1 & touch deps_loaded\n",
"![ ! -f \"deps_loaded\" ] & pip install shadowfinder >> {logfile} 2>&1 & touch deps_loaded\n",
"\n",
"from suncalc import get_position\n",
"from shadowfinder import ShadowFinder\n",
"import datetime\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import matplotlib.colors as colors\n",
"from mpl_toolkits.basemap import Basemap\n",
"\n",
"\n",
"datetime_date = datetime.datetime.strptime(date, \"%Y-%m-%d\").date()\n",
"datetime_time = datetime.datetime.strptime(time, \"%H:%M:%S\").time()\n",
"#datetime_time = datetime.time(hours, minutes, seconds)\n",
"date_time = datetime.datetime.combine(datetime_date, datetime_time, tzinfo=datetime.timezone.utc) # Date and time of interest\n",
"\n",
"\n",
"# Main calculation\n",
"# Evaluate the sun's length at a grid of points on the Earth's surface\n",
"lats = np.arange(-90, 90, 0.25)\n",
"lons = np.arange(-180, 180, 0.25)\n",
"\n",
"lons, lats = np.meshgrid(lons, lats)\n",
"\n",
"pos_obj = get_position(date_time, lons, lats)\n",
"sun_altitude = pos_obj['altitude'] # in radians\n",
"\n",
"# Calculate the shadow length\n",
"shadow_lengths = object_height / np.apply_along_axis(np.tan, 0, sun_altitude) \n",
"\n",
"# Replace points where the sun is below the horizon with nan\n",
"shadow_lengths[sun_altitude <= 0] = np.nan\n",
"\n",
"# Show the relative difference between the calculated shadow length and the observed shadow length\n",
"shadow_relative_length_difference = (shadow_lengths - shadow_length)/shadow_length\n",
"date_time = datetime.datetime.combine(\n",
" datetime_date, datetime_time, tzinfo=datetime.timezone.utc\n",
") # Date and time of interest\n",
"\n",
"\n",
"# Plotting\n",
"fig = plt.figure(figsize=(12, 6))\n",
"\n",
"# Add a simple map of the Earth\n",
"m = Basemap(projection='cyl', resolution='c')\n",
"m.drawcoastlines()\n",
"m.drawcountries()\n",
"\n",
"# Deal with the map projection\n",
"x, y = m(lons, lats)\n",
"\n",
"# Set the a color scale and only show the values between 0 and 0.2\n",
"cmap = plt.cm.inferno_r\n",
"norm = colors.BoundaryNorm(np.arange(0, 0.2, 0.02), cmap.N)\n",
"\n",
"# Plot the data\n",
"m.pcolormesh(x, y, np.abs(shadow_relative_length_difference), cmap=cmap, norm=norm,alpha=0.7)\n",
"\n",
"# plt.colorbar(label='Relative Shadow Length Difference')\n",
"plt.title(f\"Possible Locations at {date_time.strftime('%Y-%m-%d %H:%M:%S %Z')}\\n(object height: {object_height}, shadow length: {shadow_length})\")\n",
"plt.show()\n"
"finder = ShadowFinder(object_height, shadow_length, date_time)\n",
"finder.find_shadows()\n",
"finder.plot_shadows()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python"
"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"
}
},
"nbformat": 4,
Expand Down
Loading

0 comments on commit 813be09

Please sign in to comment.