Obliques
An open approach
@
boyd.xyz/a
Kuwohi (Clingmans Dome), 6,644 ft
Yesterday in the Great Smokies
Webcam
archive
Ky GIS Conference, September 25, 2025
Boyd Shearer
Senior Lecturer, UKy Geography
🔗
Assistant Director, Ky DGI (late October)
🔗
outrageGIS mapping
🔗
KyFromAbove Oblique Imagery
Open data for entire state (2022-2024)
Four views for every location in Kentucky
KyFromAbove Explorer 💯
🔗
Raw imagery
How to get it?
Why would you want it?
Gorgeous natural views!
Better sense of scale and context.
Imagine
Walking downtown and asking
"What's on the other side of this building?"
View it on your mobile device.
Challenges
Over 4.4 million images
40 MB – 100 MB per image
At roughly 10k x 15k pixels
TIFFs in the browser?
Opportunities
KyFromAbove on AWS Explorer
🔗
contains the images
but importantly:
it provides metadata via spatial layers.
Goals
Make an app that runs on mobile browsers
with minimal tech
and intuitive symbology.
Download full-res by location.
Tech
MapLibreGL JS mapping library
🔗
PMTiles for serverless vector tiles
🔗
Centroids
Create vector tile of centroids.
2.2 GB becomes 300 MB.
Symbolize by camera orientation.
Attributes needed for intuitive symbology
Time for data munging
to find which way is up
### Make the lookup table --- ```python # List of file paths to FGDBs with image frame feature classes data = [ r"Z:\data\oblique_photos_FGDB\KY_KYAPED_2022_Season2_3IN_ImageFrameEO.gdb\KY_KYAPED_2022_Season2_3IN_ImageFrameEO", r"Z:\data\oblique_photos_FGDB\KY_KYAPED_2023_Season1_3IN_ImageFrameEO.gdb\KY_KYAPED_2023_Season1_3IN_ImageFrameEO", r"Z:\data\oblique_photos_FGDB\KY_KYAPED_2023_Season2_3IN_ImageFrameEO.gdb\KY_KYAPED_2023_Season2_3IN_ImageFrameEO", r"Z:\data\oblique_photos_FGDB\KY_KYAPED_2024_Season1_3IN_ImageFrameEO.gdb\KY_KYAPED_2024_Season1_3IN_ImageFrameEO" ] ``` --- ```py # Make the empty dictionary to hold results fwdFlightDirection = { "total": 0, "North": 0, "South": 0, "Other": 0 } ``` --- ```python # Loop through each FGDB layer and calculate direction from Kappa for a in data: with arcpy.da.SearchCursor(a, ["ID","Kappa"]) as cursor: with arcpy.da.InsertCursor("flight_shot_direction", ["ID", "Direction"]) as insert_cursor: for i, row in enumerate(cursor): id_parts = row[0].split('/') direction = id_parts[1].split('_')[0] flight = id_parts[1].split('_')[1] shot = id_parts[1].split('_')[2] group = f'{flight}_{shot}' kappa = row[1] fwdFlightDirection["total"] += 1 if group not in fwdFlightDirection: if kappa > -20 and kappa < 20: fwdFlightDirection[group] = "North" fwdFlightDirection["North"] += 1 elif kappa > 160 and kappa < 200: fwdFlightDirection[group] = "South" fwdFlightDirection["South"] += 1 elif kappa > -200 and kappa < -160: fwdFlightDirection[group] = "South" fwdFlightDirection["South"] += 1 else: fwdFlightDirection[group] = "Other" fwdFlightDirection["Other"] += 1 ```
Now we know the flight directions
Preview?
TIFFs in the browser?
Thankfully: Cloud Optimized GeoTIFFs (COGs)
and geotiff.js
🔗
Process
Resample to max width of 4k
at runtime in browser
and render to browser Canvas element.
Hey, works in my browser!
Live demo
boydx.github.io/phase-3-oblique-centroids
Where's Wally?
Thank you