geolocation.md (28016B)
1 --- 2 title: "Geolocation & Chronolocation" 3 description: "Work out where a photo was taken, and when, from shadows, sun position, terrain and visible detail." 4 category: osint 5 subcategory: "Geospatial" 6 tags: [osint, geolocation, chronolocation, shadows, verification] 7 tools: [suncalc, shadowfinder, pysolar, shadowmap, shademap, geohints, qgis, gdal, exiftool] 8 difficulty: advanced 9 updated: 2026-10-04 10 references: 11 - name: "Bellingcat's Online Investigation Toolkit" 12 url: "https://bellingcat.gitbook.io/toolkit" 13 author: "Bellingcat" 14 license: none 15 relation: derived 16 note: "Tool catalogue: names, descriptions, cost flags and links for this area." 17 - name: "OSINT Newsletter Tools Library" 18 url: "https://tools.osintnewsletter.com" 19 author: "The OSINT Newsletter" 20 license: none 21 relation: derived 22 note: "Second tool catalogue, cross-checked against the above." 23 --- 24 25 ## What this covers 26 27 Placing an image on the earth and in time without metadata. The place comes from matching what is 28 in the frame against the world; the time comes from the sun, which is the one thing in the picture 29 whose behaviour is exactly calculable. A hard geolocation is hours of work, and the chronolocation 30 that follows it is arithmetic you can show someone. 31 32 ## Method 33 34 1. **Inventory the frame.** List everything identifiable: road markings, utility pole design, 35 plug sockets, licence-plate format, language and script on signage, vegetation, kerb style, 36 architecture, mountain profile. 37 2. **Narrow the region.** Those details constrain the country or region long before they give you a 38 point. Pole and bollard design alone often gets you to a handful of countries. 39 3. **Find a searchable anchor.** A business name, a phone number on a van, a street name, a bus 40 route number. One readable sign collapses the search; spend your effort on making one legible 41 before you spend it on scanning imagery. 42 4. **Match terrain.** Ridgelines are fingerprints and are visible from tens of kilometres away. 43 This is the step that works when there is no signage at all, and the step that fails on flat 44 ground. 45 5. **Confirm with imagery.** Satellite and street view from the era of the photo. Match building 46 footprints and the relative geometry of fixed objects, not the general vibe of the scene. 47 6. **Measure the shadow, then compute.** Direction gives you the sun's azimuth, the length-to-height 48 ratio gives you its elevation. Together they give a time of day and, usually, two candidate date 49 windows per year rather than one. 50 7. **Say what would falsify it.** A geolocation you cannot break is one nobody checked. Name the 51 feature that would have to be in the wrong place for your answer to be wrong. 52 53 The judgement calls: decide early whether you are doing signage work or terrain work, because they 54 need different tools and mixing them wastes hours. Decide whether the date is given or derived — 55 if you take the date from the caption and then use the sun to "confirm" the caption, you have 56 proved nothing. And decide whether a precise coordinate should be published at all, before you 57 find one. 58 59 ## The shadow arithmetic 60 61 A vertical object of height `h` casting a shadow of length `s` on level ground fixes the sun's 62 elevation angle: 63 64 ```text 65 tan(elevation) = h / s 66 67 h = 5.00 m, s = 8.20 m 68 elevation = atan(5.00 / 8.20) = atan(0.60976) = 31.37 degrees 69 70 check: 5.00 / tan(31.37 deg) = 5.00 / 0.60976 = 8.20 m 71 ``` 72 73 The shadow points directly away from the sun, so the sun's compass bearing is the shadow's bearing 74 plus 180: 75 76 ```text 77 shadow bearing measured off the image : 062 deg 78 sun azimuth : (062 + 180) mod 360 = 242 deg 79 ``` 80 81 Three things break this. The ground must be level — a shadow running downhill is longer than the 82 arithmetic expects and will push your elevation too low. The object must be vertical and you must 83 be measuring its true height, not its foreshortened height in the frame. And `elevation` here is 84 geometric; at elevations below about 5 degrees atmospheric refraction lifts the apparent sun by 85 roughly half a degree, which matters at sunrise and sunset and nowhere else. 86 87 Then the part people get wrong: an azimuth and elevation pair does **not** identify one date. The 88 sun retraces its declination on the way out of winter and on the way back in, so almost every 89 pair matches two windows in the year, roughly symmetric about the solstice. Expect two answers and 90 rule one out with something other than the sun — foliage, snow, an event in the background, the 91 clothes people are wearing. 92 93 ## Key tools 94 95 ### SunCalc 96 97 Web only, free, no account. Give it a coordinate and a date and it draws the sun's track for that 98 day with the numbers underneath: altitude, azimuth, and a **shadow length** readout for an object 99 height you type in. That last field is why it beats the generic ephemeris sites — you can go 100 straight from the measurement you took off the image to the time that produces it, without 101 converting anything. 102 103 ```text 104 1. Enter the coordinate in "Set Lat/Lon", or drag the map pin onto the spot. 105 2. Set month and date in the dropdowns. Note the timezone line (TZ) it reports; everything 106 downstream is in that zone, and it is the usual source of a one-hour error. 107 3. Drag the time slider and watch two readouts: "Azimuth" against your measured sun bearing, 108 and "Shadow length [m]" against the shadow you measured, with "at an object level [m]" 109 set to the object's real height. 110 4. When both match at once, you have a time. When only the azimuth matches, you have two 111 times -- morning and afternoon mirror each other, and length is what separates them. 112 5. "Reverse Calculation" works the other way: fix the shadow and ask for the time. 113 6. Capture the page URL. The state lives in the fragment -- 114 #/<lat>,<lon>,<zoom>/<YYYY.MM.DD>/<HH:MM>/<object height> -- so the link reproduces the 115 exact reading rather than just the tool. 116 ``` 117 118 The azimuth it reports is a compass bearing measured clockwise from north, which is what you 119 measured off the image, so no conversion is needed here. Read-offs from a slider are worth about a 120 minute of precision at best; quote a window, not a timestamp. And the whole result is conditional 121 on the coordinate — a 50 m position error barely moves the azimuth, but a wrong *date* moves it by 122 degrees per week away from the solstices, which is exactly where the two-candidate-window problem 123 bites. 124 125 ### ShadeMap and Shadowmap 126 127 Also web only. These solve the opposite problem: when the shadow in your image is cast by a 128 building or a ridge rather than by the subject, you cannot measure an object height, so SunCalc's 129 arithmetic has nothing to chew on. These render the shadows that real geometry casts at a chosen 130 moment and you match the *pattern* instead. 131 132 ```text 133 ShadeMap (shademap.app) 134 - loads OpenStreetMap building footprints plus a terrain model, so both urban and 135 mountain shadows are simulated 136 - date and time slider at the bottom; the shadow polygons redraw live 137 - the sun-exposure analytics (hourly / daily / annual) are a solar-study feature, 138 not an investigative one -- ignore them for this work 139 - free for interactive use on the site; the paid tiers are for embedding the engine 140 in your own map, not for using it here 141 142 Shadowmap (shadowmap.org) 143 - 3D buildings with cast shadows, better for dense city blocks 144 - set the date and time, then orbit the camera to the photographer's approximate 145 position and compare the shadow edges against the frame 146 - sign-in gates some of the time controls 147 148 What to capture either way: a screenshot at the matched time, the coordinate, the 149 date, the stated timezone, and the building or ridge you matched on. 150 ``` 151 152 Building shadows are only as good as the building heights in OpenStreetMap, which are frequently 153 absent, guessed, or recorded for a structure that has since been extended. Terrain shadows are more 154 trustworthy because the DEM is measured. Neither knows about trees, awnings, scaffolding or a 155 crane that was on site that month — so an unexplained shadow is a reason to doubt your position 156 before it is a reason to doubt the tool. 157 158 ### suncalc (Python) 159 160 The same model as the website, as a library, which is what you want once you are checking more than 161 one candidate. Give it a coordinate and a timestamp and it returns the sun's position; loop it over 162 a year and you get every moment that matches your measurement, which is the honest way to find both 163 date windows instead of stopping at the first one. 164 165 ```bash 166 pipx install suncalc # or: pip install suncalc pandas 167 ``` 168 169 ```python 170 import math, datetime 171 import pandas as pd 172 from suncalc import get_position, get_times 173 174 lat, lon = 38.7075, -9.1364 # candidate coordinate 175 height, shadow = 5.00, 8.20 # metres, measured off the image 176 shadow_bearing = 62.0 # degrees, measured off the image 177 178 # the two numbers the image gives you 179 elevation = math.degrees(math.atan(height / shadow)) # 31.37 180 sun_bearing = (shadow_bearing + 180) % 360 # 242.0 181 182 def sun(dt): 183 # suncalc returns radians, and its azimuth is measured from SOUTH toward west -- 184 # add 180 degrees to get the compass bearing you measured off the image 185 p = get_position(pd.Timestamp(dt), lon, lat) 186 return (math.degrees(p["azimuth"]) + 180) % 360, math.degrees(p["altitude"]) 187 188 # sweep the whole year at 5-minute resolution: this is what exposes the second window 189 start = datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc) 190 for step in range(0, 365 * 288): 191 dt = start + datetime.timedelta(minutes=5 * step) 192 az, alt = sun(dt) 193 if abs(az - sun_bearing) < 1.0 and abs(alt - elevation) < 0.5: 194 print(f"{dt:%Y-%m-%d %H:%M} UTC az {az:6.1f} elev {alt:5.1f}") 195 196 # sanity-check the day itself: if your candidate time is after dusk, the coordinate is wrong 197 t = get_times(pd.Timestamp("2026-03-22 12:00:00"), lon, lat) 198 print("sunrise", t["sunrise"], "solar noon", t["solar_noon"], "sunset", t["sunset"]) 199 ``` 200 201 Two conventions to hold onto, because getting either wrong silently produces a plausible, wrong 202 answer. Azimuth comes back in radians measured from south, so the `+ 180` above is not optional. 203 Altitude is geometric and ignores refraction. Pass timezone-aware UTC datetimes and convert to 204 local time at the very end, after you have checked whether the location observes DST on that date 205 — a shadow matched in March against a September local time is the most common way this analysis 206 goes quietly wrong. The package is thin and has not changed since 2023; that is fine for a model 207 of celestial mechanics, and it is not a sign of anything. 208 209 ### pysolar 210 211 A second implementation, which is the point of using it: run the same coordinate and timestamp 212 through both and you are checking your arithmetic rather than one library's. It is also the more 213 natural fit when you want irradiance or want to sweep without pandas in the way. 214 215 ```bash 216 pipx install pysolar # or: pip install pysolar 217 ``` 218 219 ```python 220 import datetime 221 from pysolar.solar import get_altitude, get_azimuth 222 223 lat, lon = 38.7075, -9.1364 224 when = datetime.datetime(2026, 6, 15, 12, 30, tzinfo=datetime.timezone.utc) 225 226 # pysolar's azimuth is already a compass bearing, clockwise from north -- no offset needed 227 print("altitude", get_altitude(lat, lon, when)) # 56.80 228 print("azimuth ", get_azimuth(lat, lon, when)) # 216.45 229 230 # cross-check against suncalc for the same instant: 56.79 / 216.36 231 # agreement to a tenth of a degree means the inputs are right; a whole-degree gap 232 # almost always means one of the two got a naive datetime and assumed local time 233 ``` 234 235 `get_altitude` applies a refraction correction by default, which is why it sits a hundredth of a 236 degree off suncalc's geometric value near the zenith and further off near the horizon — at 5 237 degrees elevation the two will differ by about half a degree, and pysolar is the one closer to what 238 a camera actually saw. Passing a naive `datetime` is the failure mode: it will be interpreted as 239 UTC and you will get an answer that looks fine and is hours wrong. 240 241 ### ShadowFinder 242 243 Bellingcat's inversion of the problem, for when you have no candidate coordinate at all. Feed it an 244 object height, a shadow length and a UTC timestamp and it maps every point on earth where a shadow 245 of that ratio is possible at that instant — a band, not a point, but a band you can intersect with 246 everything else you know. 247 248 ```bash 249 pipx install shadowfinder # or: pip install shadowfinder 250 251 # object height, shadow length, date, time -- POSITIONAL, in that order 252 shadowfinder find 5 8.2 2026-03-22 16:00:00 --time_format=utc 253 254 # when you already have the sun's elevation rather than a height/length pair 255 shadowfinder find_sun 31.37 2026-03-22 16:00:00 --time_format=utc 256 257 # the subcommands and their arguments, before you guess 258 shadowfinder find --help 259 shadowfinder find_sun --help 260 261 # writes a PNG map into the current directory, named for its inputs: 262 # shadow_finder_20260322-160000-Utc_5_8.2.png 263 ls shadow_finder_*.png 264 265 # the timezone grid it uses for local-time input is generated once and cached 266 shadowfinder generate_timezone_grid 267 ``` 268 269 The older `--object-height / --shadow-length / --date-time` flag form that circulates in tutorials 270 no longer exists — current releases take positional arguments under a `find` subcommand and will 271 print a bare usage line if you use the old syntax. The output band is wide, and it is a band of 272 *possibility*: it tells you where such a shadow could fall, not where this one did. It is at its 273 most useful when the timestamp is independently known — from a livestream, a broadcast, a 274 timestamped post — and least useful when you derived the timestamp from the same photo. 275 276 ### GeoHints 277 278 Web only, free, and the reference for step one. It catalogues the regional giveaways by country: 279 bollards, utility poles, traffic lights, road markings, licence plates, house numbers, sign shapes, 280 road-numbering schemes, post boxes, driving side, even the vehicles Google mounts its cameras on. 281 Built for GeoGuessr, which is precisely why it is organised the way an investigator needs — by 282 visible object rather than by country. 283 284 ```text 285 1. Pick the object in your frame that is most likely to be regulated nationally. 286 Ranked by how much they narrow things: licence plate format > road sign shape and 287 font > utility pole construction > bollard > kerb and road marking > house numbers. 288 2. Open that object's page and scan the per-country plates side by side. You are 289 looking for a disqualifier as much as a match -- "not this country" eliminates 290 faster than "maybe this one". 291 3. Cross two independent objects before you commit to a region. A pole design shared 292 by six countries and a sign font shared by four may intersect at one. 293 4. Check the Signs subsection separately: back-of-sign colour, chevron style and 294 bus-stop design are all national and all usually visible in a frame that has no 295 readable text at all. 296 5. Record which object you matched on and the page you matched it against. "Bollard 297 type matches Portugal" is checkable; "looks Iberian" is not. 298 ``` 299 300 It is a crowd-built reference, so coverage is uneven — rich for Europe and the GeoGuessr-popular 301 countries, thin for much of Africa and Central Asia, and it lags on recent sign redesigns and plate 302 reissues. Nothing on it is evidence by itself: a match narrows the search space, and the actual 303 geolocation still has to end at a specific place on a specific image. 304 305 ### QGIS and gdal_viewshed 306 307 Terrain is the fallback when there is no text in the frame. A ridgeline's silhouette depends only 308 on the observer's position, so if you can trace the horizon in the photo you can test candidate 309 positions against a DEM until one produces that profile. QGIS has carried a native **Elevation 310 Profile** panel since 3.26, which is the fastest way to do it interactively. 311 312 ```text 313 QGIS elevation profile, for matching a horizon 314 1. Load a DEM (SRTM, Copernicus GLO-30, or a national LiDAR product where one exists). 315 2. Layer Properties > Elevation > tick "Represent elevation surface". Without this the 316 profile panel will not see the layer and the panel looks broken. 317 3. View > Elevation Profile to open the panel. 318 4. "Capture curve": draw a line from your candidate camera position out along the 319 bearing the photograph faces, past the furthest ridge. 320 5. Compare the plotted skyline against the photo's horizon. Use the vertical 321 exaggeration control to match the photo's apparent relief, then set it back to 1 322 before you quote any number off it. 323 6. Wrong candidate positions fail obviously -- a peak in the wrong order, a saddle 324 that should be hidden. That is the point: this step eliminates fast. 325 ``` 326 327 For the reverse question — "could this spot have been seen from there at all" — `gdal_viewshed` 328 answers it as a raster, which is better than eyeballing when you have many candidates: 329 330 ```bash 331 # binary visibility from an observer 2 m above the surface, out to 15 km 332 # -ox/-oy are in the DEM's own CRS units, so reproject the DEM to a metric CRS first 333 gdal_viewshed -md 15000 -ox 499000 -oy 4283000 -oz 2 dem_utm.tif viewshed.tif 334 335 # the minimum height a target would need to be visible, rather than a yes/no mask 336 gdal_viewshed -om DEM -md 15000 -ox 499000 -oy 4283000 dem_utm.tif minheight.tif 337 338 # accumulate over a grid of observers: where could a photographer have stood 339 gdal_viewshed -om ACCUM -md 15000 -ox 499000 -oy 4283000 dem_utm.tif accum.tif 340 341 # target height matters when the subject is a mast or a roofline, not the ground 342 gdal_viewshed -md 20000 -tz 30 -ox 499000 -oy 4283000 dem_utm.tif mast_visible.tif 343 ``` 344 345 A DEM is a bare-earth or surface model depending on which one you downloaded, and the difference 346 decides the answer: a bare-earth model will happily tell you that you can see through a forest and 347 a city. 30 m postings smooth real ridgelines, so a profile that is close but not exact is expected 348 and is not a match. And viewshed output is geometric — it knows nothing about haze, which is what 349 actually limits how far a camera sees on the day in question. Reprojection, cropping and 350 hillshading of these rasters are covered on 351 [Maps, Satellite & Street-Level Imagery](/sheets/osint/maps-and-satellite-imagery). 352 353 ### exiftool 354 355 Before any of the above, check whether the question is already answered. Most images stripped by 356 social platforms have nothing, but files received directly from a source, pulled from a cloud 357 drive, or lifted off a messaging app's media folder frequently still carry GPS. 358 359 ```bash 360 brew install exiftool # or: apt install libimage-exiftool-perl 361 362 # GPS as a decimal pair you can paste into a map, and nothing else 363 exiftool -n -GPSLatitude -GPSLongitude -GPSAltitude photo.jpg 364 365 # the direction the camera was pointing, which turns a point into a line of sight 366 exiftool -GPSImgDirection -GPSImgDirectionRef photo.jpg 367 368 # every timestamp in the file, including the offset that tells you the local zone 369 exiftool -time:all -OffsetTime -OffsetTimeOriginal -a -G1 photo.jpg 370 371 # a whole directory into one CSV, to find which file in a set still has coordinates 372 exiftool -csv -n -GPSLatitude -GPSLongitude -DateTimeOriginal ./images > gps.csv 373 ``` 374 375 A GPS tag is a claim made by the device, not a fact: it can be a cached fix from the last place the 376 phone had signal, it can be the location the file was edited rather than shot, and it can be 377 written by hand. Treat it as a strong lead that still has to be confirmed against the frame. Full 378 metadata, container and manipulation analysis lives on 379 [Image & Video Forensics](/sheets/osint/image-video-forensics) rather than here. 380 381 ## Shadow and sun tools at a glance 382 383 | Tool | What it does | 384 | --- | --- | 385 | [SunCalc](https://www.suncalc.org/) | Sun position, azimuth, altitude and shadow length for any place, date and time. The workhorse. | 386 | [ShadowMap](https://shadowmap.org/) | 3D buildings with cast shadows rendered at a chosen time. | 387 | [ShadeMap](https://shademap.app/) | Global shadow simulation including terrain as well as buildings. | 388 | [Shadow Finder](https://github.com/bellingcat/ShadowFinder) | Inverts the problem: given shadow length and a timestamp, maps every point on earth where that shadow is possible. | 389 | [suncalc-py](https://github.com/kylebarron/suncalc-py) | The SunCalc model as a Python library, for sweeping many candidates. | 390 | [pysolar](https://pysolar.readthedocs.io/) | Independent solar-position implementation, for cross-checking. | 391 392 ## Visual reference 393 394 [GeoHints](https://geohints.com/) catalogues the regional details that narrow a location — bollards, 395 traffic lights, road markings, utility poles, licence plates — by country. 396 397 [Bellingcat's OpenStreetMap Search](https://osm-search.bellingcat.com/) and 398 [Spot](https://www.findthatspot.io/) both let you search for *relationships* between features — 399 "a church within 200m of a bridge over a river" — which is how you turn a described scene into 400 candidate coordinates. The Overpass queries underneath that idea are on 401 [Maps, Satellite & Street-Level Imagery](/sheets/osint/maps-and-satellite-imagery). 402 403 ## Tool reference 404 405 | Tool | What it does | Cost | 406 | --- | --- | --- | 407 | [Bellingcat OpenStreetMap Search](https://osm-search.bellingcat.com/) | A user interface to search OpenStreetMap data for features in proximity to each other. | free | 408 | [GeoHints](https://geohints.com/) | GeoHints is a website that provides information about things like traffic lights, utility poles, bollards etc. for different regions of the world to help… | free | 409 | [GeoNames](http://www.geonames.org/) | The GeoNames geographical database covers all countries and contains over eleven million place names that are available for download free of charge… | free | 410 | [Photo-Map.RU](http://photo-map.ru/) | Geotagged VK posts. | free | 411 | [Spot](https://www.findthatspot.io/) | A natural language interface for querying the OpenStreetMap database to find locations which meet the search criteria described by the user. | free | 412 413 ## Pitfalls 414 415 - **Satellite imagery has a date.** A building present in the photo and absent from imagery may 416 simply be newer, or demolished. Check the capture date and look for historical imagery. 417 - **Terrain matching defeats you at low elevation.** Flat terrain has no ridgeline to match, and a 418 30 m DEM will not reproduce a 10 m rise. 419 - **Shadow work needs the true timezone**, including whether DST was in force on that date in that 420 country, which is a different question from whether it is in force now. 421 - **One azimuth-and-elevation pair means two date windows.** Reporting the first one you find, and 422 not the second, is the most common way a chronolocation is wrong while being arithmetically 423 correct. 424 - **Circular reasoning.** If the date came from the caption, the sun cannot confirm the caption. 425 It can only confirm that the caption is internally consistent, which is a much weaker statement. 426 - **Publishing a precise location endangers people.** For conflict imagery especially, consider 427 whether the coordinates need to be public. 428 - **Plausible is not confirmed.** A location that fits is a hypothesis. Confirmation means a 429 specific feature matching in a specific place, named, so that someone else can go and disagree. 430 431 ## Worked example 432 433 One datum: a photograph of a public square. No metadata, no caption beyond a claim that it was 434 taken "in September". A lamp standard in the foreground casts a clean shadow across flat paving. 435 436 1. **Inventory and narrow.** The bollards, the kerb profile and the black-on-white house numbers 437 go into GeoHints object by object. The sign font and the bollard type intersect on Portugal; 438 a partial bus-stop livery in the background does not contradict it. 439 2. **Anchor.** A shopfront name is legible after a crop and upscale. Searching the name plus 440 "Lisboa" returns one address on a named square, and Street View from the same corner reproduces 441 the building line, the arcade spacing and the lamp standard. Candidate coordinate: 442 **38.7075, -9.1364**. 443 3. **Measure.** The lamp standard matches a documented 5.00 m type and its shadow, scaled against 444 the paving slabs, runs 8.20 m. The shadow's bearing, taken off the satellite image along the 445 same paving joint, is 062 degrees. 446 4. **Convert.** `atan(5.00 / 8.20)` = **31.37 degrees** solar elevation. Sun bearing = 447 `(62 + 180) mod 360` = **242 degrees**, so the sun is in the west-south-west and this is an 448 afternoon photograph. 449 5. **Sweep the year.** The `suncalc` loop above, at the candidate coordinate, with a 1-degree 450 azimuth and 0.5-degree elevation tolerance, returns seven matching instants in all of 2026, in 451 two clusters: 452 453 ```text 454 2026-03-21 16:00 UTC az 241.6 elev 31.0 455 2026-03-22 16:00 UTC az 242.0 elev 31.2 456 2026-03-23 16:00 UTC az 242.4 elev 31.5 457 2026-03-24 16:00 UTC az 242.8 elev 31.7 458 459 2026-09-20 15:45 UTC az 242.1 elev 31.8 460 2026-09-21 15:45 UTC az 241.8 elev 31.5 461 2026-09-22 15:45 UTC az 241.6 elev 31.1 462 ``` 463 464 6. **Convert to local time, carefully.** Portugal moved to UTC+1 on 29 March 2026 and back on 25 465 October. The March window is therefore **16:00 local**, and the September window is **16:45 466 local**. Same sun, different clock, entirely because of a date the arithmetic knows nothing 467 about. 468 7. **Break the tie with something that is not the sun.** The trees on the square are in full leaf 469 and the café terrace is laid out — both argue September over the third week of March. The 470 caption's "September" is now consistent with the image rather than assumed by it, because the 471 sun was computed independently and the foliage was the tiebreaker. 472 8. **Cross-check the model.** The same instant through `pysolar` returns an elevation within a 473 tenth of a degree of `suncalc`'s, which confirms the inputs rather than the conclusion. 474 475 What you can assert: a square identified by a named shopfront and reproduced building geometry, and 476 an afternoon sun position consistent with **20–22 September 2026, around 16:45 local time**, with 477 21–24 March as a rejected alternative and the reason for rejecting it stated. 478 479 What would falsify it: a shopfront that was at a different address on that date, paving that was 480 relaid between the Street View capture and the photograph (which would break the shadow 481 measurement, not the location), a lamp standard of a different height, or any evidence that the 482 foliage argument is wrong. The measurement that carries the most risk is the 8.20 m — a 10 per cent 483 error there moves the elevation by roughly 2.5 degrees and the window by several days, so quote it 484 with that tolerance or do not quote a day at all. 485 486 ## Broader catalogues 487 488 - [Geolocation and Maps OSINT](https://tools.osintnewsletter.com/tool-categories/geolocation-and-maps-osint) 489 490 491 ## More tools 492 493 Further tools for this area from the OSINT Newsletter Tools Library ([Geolocation and Maps OSINT](https://tools.osintnewsletter.com/tool-categories/geolocation-and-maps-osint)), excluding those already listed above. 494 495 | Tool | What it does | 496 | --- | --- | 497 | [Geolocation Estimation](https://labs.tib.eu/geoestimation/) | A research demonstrator from TIB that uses deep learning to estimate where a photograph was taken based on visual and geographic… | 498 499 ## Sources 500 501 Both catalogues below are maintained by other people and are considerably larger than 502 this page. Use them as the canonical index; this sheet is a working route through them. 503 504 - [Bellingcat's Online Investigation Toolkit](https://bellingcat.gitbook.io/toolkit) — ~340 tools, each with its own 505 review page covering cost, difficulty, requirements and limitations. 506 - [OSINT Newsletter Tools Library](https://tools.osintnewsletter.com) — ~280 tools, organised by investigative goal. 507 508 Neither publishes a licence, so nothing here is copied from them: tool names, one-line 509 descriptions, cost flags and links are catalogue facts, and the method and commentary are 510 this site's own. See [credits](/credits).