maps-and-satellite-imagery.md (70775B)
1 --- 2 title: "Maps, Satellite & Street-Level Imagery" 3 description: "Choose the right imagery source for the question, find historical coverage, and work with the data in QGIS." 4 category: osint 5 subcategory: "Geospatial" 6 tags: [osint, satellite, maps, gis, street-view] 7 tools: [qgis, gdal, ogr2ogr, overpass, copernicus-data-space, google-earth-pro, google-earth-engine, mapillary, earthexplorer, landsatlook, stac-client, nasa-firms, nasa-gibs] 8 difficulty: intermediate 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 - name: "GDAL programs documentation" 24 url: "https://gdal.org/en/stable/programs/" 25 author: "OSGeo" 26 relation: link-only 27 note: "Upstream reference for every gdal* and ogr* flag quoted on this page." 28 - name: "Overpass QL reference" 29 url: "https://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_QL" 30 author: "OpenStreetMap contributors" 31 relation: link-only 32 note: "Upstream reference for the Overpass settings, filters and out modes used here." 33 - name: "Copernicus Data Space OData API" 34 url: "https://documentation.dataspace.copernicus.eu/APIs/OData.html" 35 author: "Copernicus Data Space Ecosystem" 36 relation: link-only 37 note: "Upstream reference for the $filter, $orderby and attribute query syntax." 38 - name: "Mapillary API documentation" 39 url: "https://www.mapillary.com/developer/api-documentation" 40 author: "Mapillary" 41 relation: link-only 42 note: "Upstream reference for the Graph API endpoints, parameters and image fields." 43 - name: "NASA GIBS API documentation" 44 url: "https://nasa-gibs.github.io/gibs-api-docs/" 45 author: "NASA EOSDIS" 46 relation: link-only 47 note: "Upstream reference for the WMTS and WMS endpoint templates." 48 - name: "NASA FIRMS Area API" 49 url: "https://firms.modaps.eosdis.nasa.gov/api/area/" 50 author: "NASA FIRMS" 51 relation: link-only 52 note: "Upstream reference for the fire-detection API path parameters." 53 --- 54 55 ## What this covers 56 57 Which imagery to use, how to get at older coverage, and the street-level sources beyond Google. 58 The main skill is matching the source to the question — resolution, revisit frequency and archive 59 depth trade off against each other and no single provider wins on all three. 60 61 ## Method 62 63 1. **Fix the question before the source.** "Has this compound grown since 2019" and "what is the 64 writing on that sign" are answered by different satellites, and no source answers both. 65 2. **Bound the area and the dates.** A coordinate with no radius and a year with no window will 66 produce thousands of scenes and no answer. Write both down first. 67 3. **Start free and coarse.** Sentinel-2 at 10m settles most change-detection questions. Spend 68 high-resolution effort only on confirming the specific thing it points at. 69 4. **Record the capture date of every frame you use**, from the source's own metadata rather than 70 the page you found it on. An undated image supports no time-sensitive claim. 71 5. **Cross-check the base layer.** Two providers disagree on building footprints, place names and 72 borders. Say which one you used, and look at a second before concluding a feature is new. 73 6. **Measure in a metric CRS.** Distances taken in degrees are wrong by a factor that varies with 74 latitude, and the tool will not warn you. 75 7. **Pivot to street level last.** Once satellite imagery has given you candidates, Mapillary or 76 Street View either confirms the ground detail or kills the candidate in seconds. 77 78 ## Choosing a source 79 80 | Question | Source | 81 | --- | --- | 82 | What does this place look like in detail? | Google Earth Pro, Esri World Imagery — sub-metre, but infrequent | 83 | What changed between two dates? | Sentinel-2 (5-day revisit, 10m), Landsat (16-day, 30m, back to 1972) | 84 | What did it look like years ago? | Google Earth Pro's historical slider, Landsat archive | 85 | Was there a fire / flood / new construction? | Sentinel-2 false-colour composites, NASA FIRMS | 86 | What is at street level? | Google Street View, Mapillary, KartaView, Yandex Panoramas | 87 | What features exist here, as data? | OpenStreetMap via Overpass | 88 89 Free high-cadence optical imagery bottoms out around 10m per pixel. Anything finer is commercial 90 and usually costs real money, so plan around Sentinel for change detection and reserve high-res for 91 confirming a specific thing. 92 93 ## Imagery sources at a glance 94 95 Resolution is the number people quote and the least useful of the four. Revisit and archive depth 96 decide whether a question is answerable at all, and scriptability decides whether you can answer it 97 for fifty coordinates instead of one. 98 99 | Source | Resolution | Revisit | Archive back to | Scriptable access | 100 | --- | --- | --- | --- | --- | 101 | Sentinel-2 (Copernicus) | 10m visible | ~5 days | 2015 | OData and STAC, free, token for download | 102 | Sentinel-1 (radar) | 5x20m | ~6 days | 2014 | same OData catalogue | 103 | Landsat 8/9 | 30m, 15m pan | 16 days each | 1972 across the series | LandsatLook STAC, no key for search | 104 | MODIS / VIIRS | 250m–1km | sub-daily | 2000 / 2012 | GIBS WMTS and WMS, no key | 105 | Google Earth Pro | sub-metre | irregular, years apart | varies by place, often 1985 | none; desktop export only | 106 | Esri World Imagery | sub-metre | irregular | current only, no slider | XYZ tile URL | 107 | Mapillary | street level | contributor-driven | 2014 | Graph API, free token | 108 | OpenStreetMap | vector, not imagery | continuous | full edit history | Overpass API, no key | 109 110 Two consequences worth internalising. Nothing free gives you both sub-metre resolution and a dated 111 archive, which is why high-resolution work means Google Earth Pro screenshots with the status-bar 112 date, and change detection means Sentinel. And the published revisit figures are orbital, not 113 usable: see the cloud arithmetic in the worked example below, where a nominal five-day revisit 114 yielded eighteen scenes in a month and two worth opening. 115 116 ## Historical imagery 117 118 **Google Earth Pro** is free desktop software and its historical imagery slider is the most 119 accessible archive of high-resolution coverage. Note the imagery date shown at the bottom — it is 120 the single most important piece of context and the most commonly ignored. 121 122 **Landsat** goes back to 1972 and is the only free option for multi-decade change. Browse it via 123 [EarthExplorer](https://earthexplorer.usgs.gov/), covered below, or query the same archive through 124 the LandsatLook STAC API, which needs no account. 125 126 ## Street-level beyond Google 127 128 Google's coverage is deep but not universal, and its capture dates are sometimes years old: 129 130 - **Mapillary** — crowd-sourced, often covers roads Google skipped, frequently more recent. 131 - **KartaView** — similar model, strong in parts of Europe. 132 - **Yandex Panoramas** — the best coverage across Russia and Central Asia by a wide margin. 133 - **Baidu Total View** — mainland China. 134 135 Always check several; a street with no Google coverage often has Mapillary imagery. 136 137 ## Key tools 138 139 ### Overpass API 140 141 OpenStreetMap's query interface, and the reason OSM is a database rather than a picture. Give it a 142 tag combination and a bounding box and it returns the features — which is how you turn "a church 143 with a red roof next to a roundabout, somewhere in this province" into a list of candidates. 144 [Overpass Turbo](https://overpass-turbo.eu/) is the browser front end; the API behind it takes 145 `curl`, and automating it is what makes a large search tractable. 146 147 ```bash 148 # the public endpoint rejects requests without a User-Agent — this is the usual first failure 149 UA='osint-research/1.0' 150 OVERPASS='https://overpass-api.de/api/interpreter' 151 152 # every pharmacy in a bounding box (south,west,north,east) 153 curl -s -A "$UA" "$OVERPASS" --data-urlencode \ 154 'data=[out:json][timeout:25];node["amenity"="pharmacy"](52.37,4.88,52.38,4.90);out body;' 155 156 # bridges over waterways — matching a described scene to candidate locations 157 curl -s -A "$UA" "$OVERPASS" --data-urlencode \ 158 'data=[out:json][timeout:60];way["bridge"="yes"](52.3,4.8,52.4,4.95);out geom;' \ 159 | jq '.elements | length' 160 161 # two features near each other: a mosque within 200m of a school 162 curl -s -A "$UA" "$OVERPASS" --data-urlencode \ 163 'data=[out:json][timeout:90]; 164 way["amenity"="place_of_worship"]["religion"="muslim"](35.6,51.3,35.8,51.5)->.w; 165 node(around.w:200)["amenity"="school"];out center;' 166 167 # cell masts, which are mapped far more completely than people expect 168 curl -s -A "$UA" "$OVERPASS" --data-urlencode \ 169 'data=[out:json];node["man_made"="mast"]["tower:type"="communication"](48.1,16.3,48.3,16.5);out center;' 170 171 # count first, then fetch: a national-scale query that returns nothing costs you nothing 172 curl -s -A "$UA" "$OVERPASS" --data-urlencode \ 173 'data=[out:csv(::count)][timeout:120];area["ISO3166-1"="NL"]->.a;node["amenity"="fuel"](area.a);out count;' 174 175 # straight to a file your GIS can open 176 curl -s -A "$UA" "$OVERPASS" --data-urlencode \ 177 'data=[out:json][timeout:60];node["aeroway"="aerodrome"](34.0,43.0,35.0,44.5);out center;' \ 178 -o aerodromes.json 179 ``` 180 181 Four forms of the query that earn their keep once the search is more than one box. `nwr` matches 182 nodes, ways and relations in one pass, which matters because a feature mapped as a node in one 183 country is a way in the next; a `node[...]` query silently misses half of them. The `[bbox:...]` 184 setting applies to every statement that carries no explicit box, so an exploratory query stops 185 being a wall of repeated coordinates. `out:csv` with named fields lands in `awk` or a spreadsheet 186 without a `jq` filter in between. And `(newer:...)` asks OSM's own edit history what changed, 187 which is a change-detection signal that costs nothing and arrives before any satellite pass. 188 189 ```bash 190 # CSV with chosen fields: header line on, comma separator. Note the renaming -- 191 # ::id comes back as the column @id, which is what you grep for afterwards 192 curl -s -A "$UA" "$OVERPASS" --data-urlencode \ 193 'data=[out:csv(::id,::type,::lat,::lon,"name";true;",")][timeout:30]; 194 node["amenity"="pharmacy"](52.37,4.88,52.38,4.90);out center;' 195 # @id,@type,@lat,@lon,name 196 # 1819064252,node,52.3723707,4.8940844,Dam Apotheek 197 # 2720875314,node,52.3783767,4.8823525,Medicijnman Apotheek Jordaan 198 199 # nwr, so a feature mapped as a way somewhere is not missed. Counting first: 200 curl -s -A "$UA" "$OVERPASS" --data-urlencode \ 201 'data=[out:csv(::count)][timeout:60];area["ISO3166-1"="NL"]->.a; 202 nwr["amenity"="fuel"](area.a);out count;' 203 # @count 204 # 4117 205 206 # a global bbox in the settings, so each statement inherits it 207 curl -s -A "$UA" "$OVERPASS" --data-urlencode \ 208 'data=[bbox:52.3730,4.8900,52.3740,4.8915][out:json][timeout:25];nwr["historic"];out center;' 209 210 # what OSM itself says changed: buildings touched since a date, with edit metadata 211 curl -s -A "$UA" "$OVERPASS" --data-urlencode \ 212 'data=[out:json][timeout:60]; 213 way["building"](52.3730,4.8900,52.3740,4.8915)(newer:"2020-01-01T00:00:00Z");out meta center;' \ 214 | jq -r '.elements[] | "\(.timestamp) v\(.version) \(.id)"' 215 ``` 216 217 Getting the result into a GIS is the step people do by hand and should not. Overpass will emit OSM 218 XML, which GDAL's OSM driver reads directly, so one `curl` and one `ogr2ogr` produce a GeoPackage. 219 The recurse-down operator `>` is what makes the XML usable: without `(._;>;)` you get ways with no 220 node coordinates and every geometry comes out empty. 221 222 ```bash 223 # ways plus the nodes that define them, as OSM XML 224 curl -s -A "$UA" "$OVERPASS" --data-urlencode \ 225 'data=[out:xml][timeout:60];way["building"](52.3730,4.8900,52.3740,4.8915);(._;>;);out meta;' \ 226 -o area.osm 227 228 # the driver exposes five fixed layers: points, lines, multilinestrings, 229 # multipolygons, other_relations. Closed building ways land in multipolygons. 230 ogr2ogr -f GPKG area.gpkg area.osm multipolygons -nln buildings 231 232 # tag filtering needs the SQLite dialect, because tags arrive as one other_tags blob 233 ogr2ogr -f GeoJSON masts.geojson area.osm -dialect sqlite \ 234 -sql "SELECT * FROM points WHERE other_tags LIKE '%\"man_made\"=>\"mast\"%'" 235 236 # reproject on the way out, so the areas and distances you measure are in metres 237 ogr2ogr -f GPKG -t_srs EPSG:32631 buildings_utm.gpkg area.gpkg buildings 238 ``` 239 240 Queries are metered by server CPU time, not request count: a careless `[timeout:900]` over a whole 241 country will get you a 429 and then a temporary ban from the main instance. Run the count form 242 first. Because OSM is crowd-sourced, a feature's absence means nobody mapped it — never that it 243 is not there — and tag completeness varies by an order of magnitude between countries. For 244 proximity searches without writing Overpass QL by hand, Bellingcat's 245 [OpenStreetMap Search](https://osm-search.bellingcat.com/) wraps the same data. 246 247 ### GDAL 248 249 The library every GIS tool is built on, and faster than any of them from a shell. Install 250 `gdal` with your package manager (`brew install gdal`, `apt install gdal-bin`). In OSINT work it 251 does four things: tell you what a file actually contains, crop and reproject it, georeference a 252 photograph or scanned map, and build band maths like NDVI without opening a GUI. 253 254 ```bash 255 # what is in this file: CRS, extent, pixel size, bands, and the capture metadata 256 gdalinfo -stats scene.tif | head -40 257 258 # the same, machine-readable, when you are checking fifty files rather than reading one 259 gdalinfo -json scene.tif | jq '{crs: .coordinateSystem.wkt[0:60], size, bands: [.bands[].band]}' 260 261 # crop to a bounding box in the file's own CRS, so you stop moving 2GB around 262 gdal_translate -projwin 4.88 52.38 4.90 52.37 -projwin_srs EPSG:4326 scene.tif crop.tif 263 264 # the same crop by pixel window, when you are working off a screenshot's coordinates 265 gdal_translate -srcwin 2400 1850 512 512 scene.tif tile.tif 266 267 # reproject to Web Mercator for overlay on a slippy basemap 268 gdalwarp -t_srs EPSG:3857 -r cubic crop.tif crop_3857.tif 269 270 # clip to an area of interest polygon rather than a rectangle, and trim the canvas to it 271 gdalwarp -cutline aoi.gpkg -crop_to_cutline -dstalpha -overwrite scene.tif aoi_only.tif 272 273 # georeference a scanned map: four ground control points (pixel x, pixel y, lon, lat), then warp 274 gdal_translate -of GTiff -a_srs EPSG:4326 \ 275 -gcp 120 95 4.8855 52.3805 -gcp 1890 110 4.9015 52.3799 \ 276 -gcp 1875 1410 4.9010 52.3702 -gcp 135 1395 4.8860 52.3708 \ 277 scan.png scan_gcp.tif 278 gdalwarp -r cubic -t_srs EPSG:4326 -overwrite scan_gcp.tif scan_geo.tif 279 280 # mosaic a directory of tiles into one virtual raster — no copying, instant 281 gdalbuildvrt mosaic.vrt tiles/*.tif 282 283 # NDVI from Sentinel-2 bands: vegetation loss, burn scars, new earthworks 284 gdal_calc.py -A B08.jp2 -B B04.jp2 --outfile=ndvi.tif \ 285 --calc="(A.astype(float)-B)/(A.astype(float)+B+0.0001)" 286 287 # differencing two dates: --extent=intersect is what stops a silent misalignment 288 gdal_calc.py -A ndvi_may.tif -B ndvi_sep.tif --outfile=ndvi_delta.tif \ 289 --calc="B-A" --type=Float32 --extent=intersect --projectionCheck --overwrite 290 291 # hillshade from a DEM, for reading terrain in a photograph's background 292 gdaldem hillshade -z 2 dem.tif hillshade.tif 293 294 # multidirectional hillshade keeps slopes facing away from the light readable 295 gdaldem hillshade -multidirectional -compute_edges dem.tif hillshade_multi.tif 296 297 # slope in degrees, for arguing about whether a vehicle track is plausible 298 gdaldem slope -compute_edges dem.tif slope.tif 299 300 # a shareable PNG at a sane size, with the world file so it stays georeferenced 301 gdal_translate -of PNG -outsize 25% 25% -co WORLDFILE=YES crop.tif preview.png 302 ``` 303 304 The one command that settles arguments is `gdallocationinfo`: it reads the pixel value at a 305 coordinate, which turns "that looks darker" into a number you can put in a report. 306 307 ```bash 308 # the band values under one WGS84 coordinate, values only, coordinate echoed back 309 echo "4.8909 52.3738" | gdallocationinfo -wgs84 -valonly -E -field_sep , scene.tif 310 311 # a whole candidate list in one pass: stdin is read line by line 312 gdallocationinfo -wgs84 -valonly -E -field_sep , ndvi_delta.tif < candidates.txt 313 314 # the elevation under a coordinate, which is how you check a claimed camera height 315 echo "4.8909 52.3738" | gdallocationinfo -wgs84 -valonly dem.tif 316 ``` 317 318 `gdalinfo` is the honesty check: if it reports no CRS, the file is a picture and any measurement 319 you take off it is invented. Georeferencing error concentrates away from your control points, so 320 put them at the corners of the area you care about and expect metres of error, not centimetres. 321 Resampling with `-r cubic` makes imagery look better and makes pixel-level forensics worse — use 322 `-r near` when the pixels themselves are the evidence. And `gdal_calc.py` will happily difference 323 two rasters of different extents unless you say `--extent=intersect`, producing a delta image whose 324 bright edges are registration error rather than change. 325 326 ### QGIS 327 328 The desktop GIS, and where a question stops being "look at this" and becomes "measure this, 329 against these layers". Free from [qgis.org](https://www.qgis.org). Add satellite imagery as a 330 basemap with **Layer → Add Layer → Add XYZ Layer** and one of these URLs: 331 332 ```text 333 https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x} 334 https://tile.openstreetmap.org/{z}/{x}/{y}.png 335 https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z} 336 ``` 337 338 Note the `{z}/{y}/{x}` order on the Esri service and `{z}/{x}/{y}` on the others — swapped axes 339 are the reason a basemap loads as noise. The Georeferencer (**Layer → Georeferencer**) does the 340 same job as the `gdal_translate -gcp` run above with a point-and-click interface and a visible 341 residual error per point, which is worth the GUI on its own. 342 343 Everything in Processing also runs headless, which is how a one-off analysis becomes repeatable: 344 345 ```bash 346 # every algorithm available, including the ones your installed plugins add 347 qgis_process list 348 349 # the parameters for one algorithm, before you guess at them 350 qgis_process help native:buffer 351 352 # machine-readable, for building a pipeline against the real parameter names 353 qgis_process --json help native:extractbylocation | jq '.parameters | keys' 354 355 # a 200m buffer around candidate points 356 qgis_process run native:buffer -- INPUT=candidates.gpkg DISTANCE=200 OUTPUT=buffered.gpkg 357 358 # reproject a layer to a metric CRS so distances mean something 359 qgis_process run native:reprojectlayer -- \ 360 INPUT=candidates.geojson TARGET_CRS='EPSG:3857' OUTPUT=candidates_3857.gpkg 361 362 # centroids of building polygons, for matching against a geotagged photo set 363 qgis_process run native:centroids -- INPUT=buildings.gpkg OUTPUT=centroids.gpkg 364 365 # keep only the features inside an area of interest. PREDICATE is an enum, not a word: 366 # 0 intersect, 1 contain, 2 disjoint, 3 equal, 4 touch, 5 overlap, 6 are within, 7 cross 367 qgis_process run native:extractbylocation -- \ 368 INPUT=centroids.gpkg PREDICATE=0 INTERSECT=aoi.gpkg OUTPUT=inside.gpkg 369 370 # units are a run-time choice, so a buffer in metres has to say so 371 qgis_process run native:buffer --distance_units=meters --area_units=m2 \ 372 --ellipsoid=EPSG:7030 -- INPUT=candidates.gpkg DISTANCE=200 OUTPUT=buffered.gpkg 373 374 # against a project, so layer references and saved styles resolve 375 qgis_process run native:centroids --project_path=case.qgz -- \ 376 INPUT=buildings.gpkg OUTPUT=centroids.gpkg 377 378 # parameters as JSON on stdin, which is how this goes into a script without quoting pain 379 echo '{"inputs": {"INPUT": "candidates.gpkg", "DISTANCE": 200, "OUTPUT": "buffered.gpkg"}}' \ 380 | qgis_process run native:buffer - 381 382 # startup is dominated by plugin loading; skip it for a batch of a hundred runs 383 qgis_process --no-python --skip-loading-plugins run native:centroids -- \ 384 INPUT=buildings.gpkg OUTPUT=centroids.gpkg 385 ``` 386 387 Measurements in a geographic CRS (`EPSG:4326`) are in degrees, not metres, and QGIS will happily 388 give you a meaningless number. Reproject to a local metric CRS or a UTM zone before you measure 389 anything you intend to publish, and say which CRS you used. `--skip-loading-plugins` is not free of 390 consequence either: an algorithm provided by a plugin disappears from `list` when you pass it, and 391 the failure reads as a missing algorithm rather than a missing plugin. 392 393 ### Copernicus Data Space 394 395 The free Sentinel archive, and the only no-cost source with a revisit frequency short enough for 396 change detection. Sentinel-2 gives 10m optical every five days; Sentinel-1 is radar and sees 397 through cloud. The catalogue is searchable without an account; downloading needs a free 398 registration. Note that Sentinel Hub Playground and the old EO Browser are retired — the browser 399 is now [Copernicus Browser](https://browser.dataspace.copernicus.eu/). 400 401 ```bash 402 CAT='https://catalogue.dataspace.copernicus.eu/odata/v1/Products' 403 404 # what Sentinel-2 exists for a date window — no token needed for search 405 curl -s -G "$CAT" \ 406 --data-urlencode "\$filter=Collection/Name eq 'SENTINEL-2' and ContentDate/Start gt 2026-09-01T00:00:00.000Z and ContentDate/Start lt 2026-09-05T00:00:00.000Z" \ 407 --data-urlencode '$top=5' | jq -r '.value[] | "\(.ContentDate.Start) \(.Name)"' 408 409 # the same, bounded to an area of interest 410 curl -s -G "$CAT" \ 411 --data-urlencode "\$filter=Collection/Name eq 'SENTINEL-2' and OData.CSC.Intersects(area=geography'SRID=4326;POLYGON((4.85 52.36,4.95 52.36,4.95 52.40,4.85 52.40,4.85 52.36))') and ContentDate/Start gt 2026-08-01T00:00:00.000Z" \ 412 --data-urlencode '$top=10' | jq -r '.value[].Name' 413 414 # radar instead, for a cloudy week 415 curl -s -G "$CAT" \ 416 --data-urlencode "\$filter=Collection/Name eq 'SENTINEL-1' and ContentDate/Start gt 2026-09-01T00:00:00.000Z" \ 417 --data-urlencode '$top=5' | jq -r '.value[].Name' 418 419 # a download needs a token from the Keycloak identity service 420 export ACCESS_TOKEN=$(curl -s -d 'client_id=cdse-public' -d "username=$CDSE_USER" \ 421 -d "password=$CDSE_PASS" -d 'grant_type=password' \ 422 'https://identity.dataspace.copernicus.eu/auth/realms/CDSE/protocol/openid-connect/token' \ 423 | jq -r .access_token) 424 425 # then fetch the product by its Id 426 curl -s -L -H "Authorization: Bearer $ACCESS_TOKEN" \ 427 "$CAT(08f7cbba-56c6-4730-b2d1-63ee8a5b9536)/\$value" -o product.zip 428 ``` 429 430 Three refinements turn that from a listing into a search. `contains(Name,'MSIL2A')` restricts to 431 the atmospherically corrected processing level, which is the only level you may compare between 432 dates. The attribute form filters on cloud cover, which is the single number that decides whether a 433 scene is worth downloading. And `$count=True` reports the total so you learn how many scenes exist 434 before you page through them. 435 436 ```bash 437 POLY="POLYGON((4.885 52.370,4.897 52.370,4.897 52.378,4.885 52.378,4.885 52.370))" 438 439 # L2A only, over the polygon, under 10 per cent cloud, oldest first, with a total count. 440 # The attribute syntax is verbose and exact: the value type appears twice. 441 curl -s -G "$CAT" \ 442 --data-urlencode "\$filter=Collection/Name eq 'SENTINEL-2' and contains(Name,'MSIL2A') and OData.CSC.Intersects(area=geography'SRID=4326;$POLY') and ContentDate/Start gt 2026-05-01T00:00:00.000Z and ContentDate/Start lt 2026-06-01T00:00:00.000Z and Attributes/OData.CSC.DoubleAttribute/any(att:att/Name eq 'cloudCover' and att/OData.CSC.DoubleAttribute/Value lt 10.00)" \ 443 --data-urlencode '$orderby=ContentDate/Start asc' \ 444 --data-urlencode '$count=True' --data-urlencode '$top=3' \ 445 | jq -r '"count: \(.["@odata.count"])", (.value[] | "\(.ContentDate.Start[0:19]) \(.Name)")' 446 447 # paging: the response carries @odata.nextLink, already signed with every parameter. 448 # Follow it rather than incrementing $skip by hand. 449 curl -s -G "$CAT" \ 450 --data-urlencode "\$filter=Collection/Name eq 'SENTINEL-1' and ContentDate/Start gt 2026-09-01T00:00:00.000Z" \ 451 --data-urlencode '$count=True' --data-urlencode '$top=20' \ 452 | jq -r '.["@odata.nextLink"]' 453 454 # the Id, which is the only thing the download endpoint accepts 455 curl -s -G "$CAT" \ 456 --data-urlencode "\$filter=contains(Name,'S2A_MSIL2A_20260501T104651')" \ 457 | jq -r '.value[] | "\(.Id) \(.ContentLength) \(.Online)"' 458 ``` 459 460 Filter on `Collection/Name` or the query will be slow enough to time out. A Sentinel-2 product 461 name encodes the tile and the processing level — `L2A` is atmospherically corrected and what you 462 want for comparing two dates; `L1C` is not. The cloud percentage in the metadata is for the whole 463 100km tile, so a "12% cloud" scene can still be solid cloud over your target, and the inverse also 464 bites: a 60 per cent scene can be perfectly clear over your 500m box. Filter on it to rank, never 465 to exclude outright. `Online: false` means the product has been moved to cold storage and the 466 download will stall rather than fail. Tokens expire in minutes; re-request rather than caching them. 467 468 ### Google Earth Pro 469 470 Web and desktop, free, and still the most accessible archive of sub-metre historical imagery. The 471 desktop build is the one worth having, because the browser version hides the two features that 472 matter. 473 474 The historical slider (**View → Historical Imagery**, or the clock icon) steps through every 475 coverage date for the view. Work it deliberately: note the date stamp in the status bar for every 476 frame you use, because that stamp is the whole evidentiary value of the screenshot. The elevation 477 profile (draw a path, then **Edit → Show Elevation Profile**) answers "could you see X from Y", 478 which is a common verification question that imagery alone cannot settle. 479 480 ```text 481 Ruler tool line and path lengths, plus a bearing in degrees 482 Polygon + area rooftop and compound areas, for matching a described structure 483 Show Elevation Profile line-of-sight and terrain between two points 484 Sun / lighting slider shadow direction at a chosen date and time 485 Import KML/GPX drop in Overpass output or a track for overlay 486 Status-bar date stamp the capture date of the imagery currently drawn 487 ``` 488 489 There is no command line, so the scriptable surface is KML, which Earth Pro reads and writes. Two 490 documents are worth keeping as templates. A `Placemark` with a `LookAt` reproduces an exact view — 491 position, bearing and camera distance — so a colleague opens the same frame rather than the same 492 coordinate. A `GroundOverlay` drapes a georeferenced scan or a cropped Sentinel tile over the 493 imagery at a stated extent, which is how you compare your own raster against Earth Pro's archive 494 without leaving Earth Pro. 495 496 ```xml 497 <?xml version="1.0" encoding="UTF-8"?> 498 <kml xmlns="http://www.opengis.net/kml/2.2"> 499 <Document> 500 <!-- reproduces a view, not just a point: heading is the bearing the camera 501 faces, tilt 0 is straight down, range is metres from the target --> 502 <Placemark> 503 <name>Warehouse, south elevation</name> 504 <LookAt> 505 <longitude>4.8909</longitude> 506 <latitude>52.3738</latitude> 507 <altitude>0</altitude> 508 <heading>312</heading> 509 <tilt>65</tilt> 510 <range>400</range> 511 </LookAt> 512 <TimeSpan> 513 <begin>2026-05-01</begin> 514 <end>2026-09-30</end> 515 </TimeSpan> 516 <Point><coordinates>4.8909,52.3738,0</coordinates></Point> 517 </Placemark> 518 519 <!-- drape your own raster over Earth Pro's imagery. The LatLonBox must match 520 the file's real extent: gdalinfo prints it, and guessing it shifts 521 everything you then "measure" off the overlay --> 522 <GroundOverlay> 523 <name>Sentinel-2 L2A crop, 2026-05-01</name> 524 <color>b4ffffff</color> 525 <drawOrder>1</drawOrder> 526 <Icon><href>crop_wgs84.png</href></Icon> 527 <altitudeMode>clampToGround</altitudeMode> 528 <LatLonBox> 529 <north>52.378</north> 530 <south>52.370</south> 531 <east>4.897</east> 532 <west>4.885</west> 533 <rotation>0</rotation> 534 </LatLonBox> 535 </GroundOverlay> 536 </Document> 537 </kml> 538 ``` 539 540 ```bash 541 # produce the overlay PNG the KML above expects: WGS84, so the LatLonBox is honest 542 gdalwarp -t_srs EPSG:4326 -r near crop.tif crop_wgs84.tif 543 gdal_translate -of PNG crop_wgs84.tif crop_wgs84.png 544 545 # read the extent back out, in the order the LatLonBox wants it 546 gdalinfo -json crop_wgs84.tif \ 547 | jq -r '.wgs84Extent.coordinates[0] | "west \(.[0][0]) south \(.[0][1]) east \(.[2][0]) north \(.[2][1])"' 548 ``` 549 550 Sharing a view outside Earth Pro has a documented URL form as well, which is the fastest way to 551 hand someone a Street View frame at a specific bearing rather than a coordinate they then have to 552 orient themselves in. 553 554 ```text 555 https://www.google.com/maps/@?api=1&map_action=pano&viewpoint=52.3738,4.8909&heading=312&pitch=0&fov=80 556 https://www.google.com/maps/@?api=1&map_action=map¢er=52.3738,4.8909&zoom=18&basemap=satellite 557 558 api=1 required, and the URL silently misbehaves without it 559 viewpoint lat,lon -- Google snaps to the nearest panorama, which may be the wrong street 560 pano a specific panorama id, when you need that exact capture and not the nearest 561 heading -180 to 360, compass bearing the camera faces 562 pitch -90 to 90, 0 is horizontal 563 fov 10 to 100, default 90 -- narrowing it is the closest thing to a zoom 564 zoom 0 to 21 on map_action=map 565 basemap roadmap, satellite or terrain 566 ``` 567 568 The displayed date is the date of the *dominant* image in the view; a mosaic can blend captures 569 months apart, with a visible seam. Zooming changes which image is drawn, so the date can change 570 under you without the view appearing to move. 3D buildings are models, not imagery, and are not 571 evidence of anything. A `viewpoint` link resolves to whatever panorama is nearest at the time 572 someone opens it, so for anything you intend to cite, capture the `pano` id instead — the nearest 573 panorama changes when Google drives the street again. 574 575 ### Google Earth Engine 576 577 The free analysis platform for the same archives, and the right tool when the question spans more 578 scenes than you want to download. The [code editor](https://code.earthengine.google.com/) runs 579 JavaScript server-side against the full Sentinel and Landsat catalogues; a free account is needed 580 and non-commercial use is free. The pattern is always the same: a geometry, a collection, filters, 581 then a composite or a difference. 582 583 ```javascript 584 // an area of interest, not a point: filterBounds takes any geometry 585 var aoi = ee.Geometry.Rectangle([4.885, 52.370, 4.897, 52.378]); 586 587 // the harmonised collection, because the pre-2022 and post-2022 scenes are 588 // otherwise offset by a processing baseline change and every difference is wrong 589 var s2 = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED') 590 .filterBounds(aoi) 591 .filterDate('2026-05-01', '2026-06-01') 592 .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 20)); 593 594 print('scenes matched', s2.size()); 595 596 // the least cloudy scene in the window, rather than the first one returned 597 var best = ee.Image(s2.sort('CLOUDY_PIXEL_PERCENTAGE').first()); 598 print('chosen', best.get('system:index'), best.get('CLOUDY_PIXEL_PERCENTAGE')); 599 600 Map.setCenter(4.891, 52.374, 16); 601 Map.addLayer(best, {bands: ['B4', 'B3', 'B2'], min: 0, max: 3000}, 'true colour'); 602 603 // false colour: vegetation bright red, bare ground and new works pale. 604 // This is the composite that makes a demolition or an earthwork obvious at 10m. 605 Map.addLayer(best, {bands: ['B8', 'B4', 'B3'], min: 0, max: 4000}, 'false colour'); 606 607 // a two-date NDVI difference, which is change detection without downloading anything 608 var ndvi = function (img) { 609 return img.normalizedDifference(['B8', 'B4']).rename('ndvi'); 610 }; 611 var may = ndvi(ee.Image(s2.sort('CLOUDY_PIXEL_PERCENTAGE').first())); 612 var sep = ndvi(ee.Image( 613 ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED') 614 .filterBounds(aoi) 615 .filterDate('2026-09-01', '2026-10-01') 616 .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 40)) 617 .sort('CLOUDY_PIXEL_PERCENTAGE') 618 .first())); 619 620 Map.addLayer(sep.subtract(may).clip(aoi), 621 {min: -0.6, max: 0.6, palette: ['red', 'white', 'green']}, 'NDVI delta'); 622 ``` 623 624 The thing to distrust here is the compositing. A median over a date range looks clean and is not an 625 image of any moment — it is a statistic, and it cannot carry a capture date, so it supports no 626 time-sensitive claim. Pick a single scene and name it when the claim is about a date. 627 `CLOUDY_PIXEL_PERCENTAGE` is the whole-tile figure again, so a 15 per cent scene can still be 628 clouded over your box; look at the picture before you trust the number. And `Map.addLayer` stretches 629 reflectance to a `min`/`max` you chose, so two dates displayed with different stretches will look 630 different whether or not anything changed. 631 632 ### Mapillary 633 634 Crowd-sourced street-level imagery, frequently covering roads Google never drove and often more 635 recent. Free API, and unlike Street View it is queryable by bounding box and capture date, which 636 makes "what did this junction look like in March" a scriptable question. 637 638 ```bash 639 # token from mapillary.com/dashboard/developers — the prefix is literally "MLY|" 640 TOKEN='MLY|xxxx|xxxx' 641 API='https://graph.mapillary.com' 642 643 # images in a bounding box (left,bottom,right,top) — must be under 0.01 degrees square 644 curl -s -H "Authorization: OAuth $TOKEN" \ 645 "$API/images?bbox=4.890,52.372,4.895,52.375&fields=id,captured_at,compass_angle,geometry&limit=50" \ 646 | jq -r '.data[] | "\(.captured_at) \(.id)"' 647 648 # only images from a date window, which is the point of using this over Street View 649 curl -s -H "Authorization: OAuth $TOKEN" \ 650 "$API/images?bbox=4.890,52.372,4.895,52.375&start_captured_at=2026-03-01T00:00:00Z&end_captured_at=2026-04-01T00:00:00Z&fields=id,captured_at" 651 652 # one image in full, including the direction the camera faced and the original file 653 curl -s -H "Authorization: OAuth $TOKEN" \ 654 "$API/IMAGE_ID?fields=id,captured_at,compass_angle,camera_type,thumb_2048_url,geometry" 655 656 # the detected objects in a frame — traffic signs are the usable ones for geolocation 657 curl -s -H "Authorization: OAuth $TOKEN" \ 658 "$API/images?bbox=4.890,52.372,4.895,52.375&fields=id,detections.value" 659 660 # download the frame you are going to cite 661 curl -s -H "Authorization: OAuth $TOKEN" "$API/IMAGE_ID?fields=thumb_2048_url" \ 662 | jq -r .thumb_2048_url | xargs curl -s -o frame.jpg 663 ``` 664 665 Four more parameters are worth knowing. `is_pano` separates 360-degree captures, which are the 666 ones where you can look behind the camera; `creator_username` and `organization_id` let you decide 667 how much to trust a sequence by who uploaded it; and `sequence_ids` plus the `image_ids` endpoint 668 walks a single drive in capture order, which is what turns a set of frames into a direction of 669 travel. 670 671 ```bash 672 # 360 captures only, with the computed fields alongside the raw ones 673 curl -s -H "Authorization: OAuth $TOKEN" \ 674 "$API/images?bbox=4.890,52.372,4.895,52.375&is_pano=true&fields=id,captured_at,is_pano,compass_angle,computed_compass_angle,computed_geometry&limit=100" \ 675 | jq -r '.data[] | "\(.captured_at) raw \(.compass_angle) computed \(.computed_compass_angle)"' 676 677 # who uploaded what here: trust a municipal fleet differently from one contributor 678 curl -s -H "Authorization: OAuth $TOKEN" \ 679 "$API/images?bbox=4.890,52.372,4.895,52.375&fields=id,captured_at,creator,organization" \ 680 | jq -r '.data[] | "\(.captured_at) \(.creator.username // "-") \(.organization // "-")"' 681 682 # every image id in one sequence, in capture order. This endpoint ignores `fields`. 683 curl -s -H "Authorization: OAuth $TOKEN" "$API/image_ids?sequence_id=SEQUENCE_ID" \ 684 | jq -r '.data[].id' | head -20 685 686 # the sequence a frame belongs to, then the frames either side of it 687 curl -s -H "Authorization: OAuth $TOKEN" "$API/IMAGE_ID?fields=sequence,captured_at" 688 689 # detections on one frame as a separate edge, with geometry per detection 690 curl -s -H "Authorization: OAuth $TOKEN" \ 691 "$API/IMAGE_ID/detections?fields=value,geometry,created_at" \ 692 | jq -r '.data[] | .value' | sort | uniq -c | sort -rn 693 ``` 694 695 The bounding box limit is real: anything larger than 0.01 degrees square is rejected rather than 696 truncated, so tile your area. `limit` defaults to 2000 and tops out there, so a dense city tile can 697 be truncated without an error — compare the count you get against the count you expected rather 698 than assuming you have everything. `compass_angle` is the camera bearing and is what lets you say 699 which side of the street a feature is on; `computed_compass_angle` and `computed_geometry` are the 700 structure-from-motion refinements and are usually the better of the two, but they exist only for 701 frames that were successfully reconstructed, so a `null` there is a frame whose position is raw GPS. 702 Coverage is contributor-driven, so a road can have 2019 and 2026 imagery and nothing between, and 703 sequence positions are GPS traces — metres of error in cities, more in canyons. 704 705 ### EarthExplorer and the LandsatLook STAC API 706 707 Web only, free with registration, and the only practical route to two archives nothing else 708 carries: Landsat back to 1972, and declassified US reconnaissance imagery from the 1960s to the 709 1980s at resolutions that still surprise people. 710 711 Search at [earthexplorer.usgs.gov](https://earthexplorer.usgs.gov/). The interface is four tabs 712 and the order matters: 713 714 ```text 715 1 Search Criteria draw a polygon or enter coordinates, then set the date range 716 2 Data Sets Landsat → Landsat Collection 2 Level-2 for analysis-ready scenes 717 Declassified Data → Declass 1/2/3 for CORONA, ARGON, KH-7 and KH-9 718 3 Additional Criteria cloud cover ceiling, and the sensor for declassified missions 719 4 Results the footprint icon shows coverage; the browse icon previews it 720 ``` 721 722 For the Landsat half of that, there is a scriptable route that needs no account at all. USGS runs a 723 STAC API at `landsatlook.usgs.gov/stac-server`, and search is open. This is the fastest way to find 724 out whether a usable Landsat scene exists over a point in a window, and the response carries the 725 sun azimuth and elevation per scene, which is the shadow geometry you need for 726 [chronolocation](/sheets/osint/geolocation) without computing anything. 727 728 ```bash 729 STAC='https://landsatlook.usgs.gov/stac-server' 730 731 # what collections exist, and their ids. landsat-c2l2-sr is Level-2 surface reflectance. 732 curl -s "$STAC/collections" | jq -r '.collections[] | "\(.id) \(.title)"' 733 734 # a search: bbox, date window, cloud ceiling, oldest first 735 curl -s -X POST "$STAC/search" -H 'Content-Type: application/json' -d '{ 736 "collections": ["landsat-c2l2-sr"], 737 "bbox": [4.885, 52.370, 4.897, 52.378], 738 "datetime": "2026-05-01T00:00:00Z/2026-09-30T23:59:59Z", 739 "query": {"eo:cloud_cover": {"lt": 20}}, 740 "sortby": [{"field": "properties.datetime", "direction": "asc"}], 741 "limit": 5 742 }' | jq -r '"matched: \(.numberMatched)", 743 (.features[] | "\(.properties.datetime[0:19]) \(.id) cloud \(.properties["eo:cloud_cover"]) sun_az \(.properties["view:sun_azimuth"]) sun_el \(.properties["view:sun_elevation"])")' 744 745 # the asset list for one scene: per-band COGs, addressable without downloading the scene 746 curl -s "$STAC/collections/landsat-c2l2-sr/items/LC09_L2SP_199023_20260528_20260530_02_T1_SR" \ 747 | jq -r '.assets | to_entries[] | "\(.key) \(.value.href)"' | head -20 748 ``` 749 750 The same API from a shell, via `pystac-client`, which handles paging and saves a searchable item 751 collection. `--matched` asks only for the count, which is the cheap question to ask first. 752 753 ```bash 754 pipx install pystac-client # or: pip install pystac-client 755 756 # how many scenes exist, before fetching any of them 757 stac-client search "$STAC" -c landsat-c2l2-sr \ 758 --bbox 4.885 52.370 4.897 52.378 --datetime 2026-05-01/2026-09-30 --matched 759 760 # the same search, cloud-filtered, newest first, saved to a file 761 stac-client search "$STAC" -c landsat-c2l2-sr \ 762 --bbox 4.885 52.370 4.897 52.378 --datetime 2026-05-01/2026-09-30 \ 763 --query "eo:cloud_cover<20" --sortby "-properties.datetime" \ 764 --max-items 20 --save landsat_items.json 765 766 # only the fields you need, piped straight into a table 767 stac-client search "$STAC" -c landsat-c2l2-sr \ 768 --bbox 4.885 52.370 4.897 52.378 --datetime 2026-07-01/2026-07-31 \ 769 --fields "id,properties.datetime,properties.eo:cloud_cover" \ 770 | jq -r '.features[] | [.id, .properties.datetime, .properties["eo:cloud_cover"]] | @tsv' 771 772 # an arbitrary polygon instead of a box: a GeoJSON file or an inline geometry 773 stac-client search "$STAC" -c landsat-c2l2-sr --intersects aoi.geojson \ 774 --datetime 2026-01-01/2026-12-31 --matched 775 ``` 776 777 Register before you search EarthExplorer, because the download buttons are hidden until you log in, 778 and read the scene's entity ID — it encodes the mission and date, and it is what you cite. 779 Declassified frames arrive as scanned film with no georeferencing at all, which is where the 780 `gdal_translate -gcp` workflow above earns its keep. Landsat Collection 2 Level-2 is corrected and 781 comparable between dates; Level-1 is not, so do not difference the two. The STAC route covers only 782 Landsat: the declassified archive is not in it, so that half of the job stays in the web interface. 783 784 ### NASA FIRMS 785 786 Active-fire detections from MODIS and VIIRS, as a CSV you can query by box and date. This is the 787 only source on this page that answers "was there a fire or an explosion here, and when" to the 788 hour, because the thermal sensors pass several times a day where the optical archive manages one 789 cloud-free frame a week. A free map key from the FIRMS site is the only requirement. 790 791 ```bash 792 KEY='your_map_key' 793 FIRMS='https://firms.modaps.eosdis.nasa.gov/api' 794 795 # which dates each product actually covers, before you ask for one that does not exist 796 curl -s "$FIRMS/data_availability/csv/$KEY/ALL" 797 798 # detections in an area over the last 3 days. AREA is west,south,east,north -- 799 # the opposite order to Overpass, and the usual source of an empty result set 800 curl -s "$FIRMS/area/csv/$KEY/VIIRS_SNPP_NRT/34.0,31.0,36.5,33.5/3" 801 802 # a historical window: same path plus a start date, YYYY-MM-DD. Day range is 1 to 5, 803 # so a month is six requests, not one. 804 curl -s "$FIRMS/area/csv/$KEY/VIIRS_NOAA20_NRT/34.0,31.0,36.5,33.5/5/2026-09-01" 805 806 # the whole globe for a day, when you do not yet know where to look 807 curl -s "$FIRMS/area/csv/$KEY/MODIS_NRT/world/1" 808 809 # high-confidence night-time detections only, sorted by brightness 810 curl -s "$FIRMS/area/csv/$KEY/VIIRS_SNPP_NRT/34.0,31.0,36.5,33.5/3" \ 811 | awk -F, 'NR==1 || ($14=="h" && $12=="N")' | sort -t, -k3 -rn | head 812 ``` 813 814 Read the columns before you read the map. `confidence` is `l`/`n`/`h` for VIIRS and a percentage 815 for MODIS, `daynight` is `D` or `N`, `frp` is fire radiative power in megawatts, and `scan`/`track` 816 give the pixel footprint — which is 375m for VIIRS and 1km for MODIS, so a detection is an area, not 817 a point, and plotting it as a dot overstates your precision by hundreds of metres. The `_NRT` 818 sources are near-real-time and get reprocessed; the `_SP` standard-product versions are the ones to 819 cite weeks later, and they will not agree exactly. A detection is a thermal anomaly: gas flares, 820 industrial furnaces and sunglint off metal roofs all produce them, so the question a FIRMS hit 821 answers is "was something hot here at 22:14 UTC", not "was there an airstrike". Conflict-specific 822 use of this feed is on [Conflict & Environment](/sheets/osint/conflict-and-environment). 823 824 ### NASA Worldview and GIBS 825 826 Worldview is the browser ([worldview.earthdata.nasa.gov](https://worldview.earthdata.nasa.gov/)) 827 and GIBS is the tile service behind it. The resolution is coarse — 250m at best — and that is not 828 the point: GIBS serves a dated, global, cloud-free-ish image for *every single day* back years, 829 with no key and no account, which no other source on this page does. It is how you establish what 830 the weather was doing on the day your high-resolution scene is missing. 831 832 ```bash 833 GIBS='https://gibs.earthdata.nasa.gov' 834 835 # one WMTS tile, REST form: 836 # /wmts/{projection}/best/{layer}/default/{time}/{tilematrixset}/{z}/{row}/{col}.{ext} 837 curl -s -o tile.jpg \ 838 "$GIBS/wmts/epsg4326/best/VIIRS_SNPP_CorrectedReflectance_TrueColor/default/2026-09-20/250m/6/13/36.jpg" 839 840 # a bounded image via WMS instead, which is what you want for an area of interest. 841 # Version 1.3.0 uses CRS and, for EPSG:4326, BBOX in lat,lon order: south,west,north,east 842 curl -s -o scene.png "$GIBS/wms/epsg4326/best/wms.cgi?\ 843 version=1.3.0&service=WMS&request=GetMap&format=image/png&STYLE=default\ 844 &CRS=EPSG:4326&BBOX=52.3,4.8,52.5,5.0&WIDTH=1200&HEIGHT=1200&TIME=2026-09-20\ 845 &LAYERS=VIIRS_SNPP_CorrectedReflectance_TrueColor" 846 847 # the layer catalogue, including each layer's available date range 848 curl -s "$GIBS/wmts/epsg4326/best/1.0.0/WMTSCapabilities.xml" \ 849 | grep -oE '<ows:Identifier>[^<]+' | sed 's/.*>//' | head -40 850 ``` 851 852 The WMS endpoint is a GDAL data source, so the day-by-day archive can be pulled straight into the 853 same pipeline as everything else, which beats screenshotting the browser: 854 855 ```bash 856 # GDAL reads the WMS URL directly, so crop and reproject in one step 857 gdal_translate -of GTiff -projwin 4.8 52.5 5.0 52.3 -projwin_srs EPSG:4326 \ 858 "WMS:$GIBS/wms/epsg4326/best/wms.cgi?LAYERS=VIIRS_SNPP_CorrectedReflectance_TrueColor&TIME=2026-09-20&SRS=EPSG:4326&FORMAT=image/png&VERSION=1.1.1" \ 859 day.tif 860 861 # a week of daily frames, to find the cloud-free day worth paying attention to 862 for d in 2026-09-{14..20}; do 863 curl -s -o "gibs_$d.png" "$GIBS/wms/epsg4326/best/wms.cgi?\ 864 version=1.3.0&service=WMS&request=GetMap&format=image/png&STYLE=default\ 865 &CRS=EPSG:4326&BBOX=52.0,4.0,53.0,5.5&WIDTH=800&HEIGHT=800&TIME=$d\ 866 &LAYERS=VIIRS_SNPP_CorrectedReflectance_TrueColor" 867 done 868 ``` 869 870 Projections are separate endpoints — `epsg4326`, `epsg3857`, `epsg3413` and `epsg3031` — and a 871 layer present in one is not necessarily present in another, with the polar projections carrying far 872 fewer. WMS 1.3.0 reverses the `BBOX` axis order for EPSG:4326 relative to 1.1.1, which is the 873 reason a request returns ocean when you asked for land; if the image looks like the wrong 874 hemisphere, swap the pairs rather than doubting the coordinates. `TIME` is a date, not a timestamp, 875 and the frame you get is the composite for that day's overpasses, so a "2026-09-20" image spans 876 hours. At 250m a building is a fifth of a pixel: use this to date weather and smoke plumes, never 877 to identify a structure. 878 879 ### SunCalc and ShadeMap 880 881 Web only, free, and the fastest way to put a time on a photograph you have already geolocated. A 882 shadow's direction and length at a known place is a clock, and these two read it in opposite 883 directions. 884 885 [SunCalc](https://www.suncalc.org/) takes a coordinate and a date and draws the sun's azimuth and 886 elevation through the day; you match the shadow bearing in the image to the time that produces it. 887 [ShadeMap](https://shademap.app) does the inverse, simulating the shadows that buildings and 888 terrain actually cast at a chosen moment, which is what you need in a city where the shadow comes 889 off a tower rather than the subject. 890 891 ```text 892 Input: coordinate, date, and the shadow bearing measured off the image 893 Read: the time or times of day whose azimuth matches that bearing 894 Check: shadow LENGTH against solar elevation — bearing alone gives two candidate times 895 Capture: the coordinate, date, computed azimuth and elevation, and the tool's own URL 896 Caveat: a date you have not independently established makes the whole result circular 897 ``` 898 899 SunCalc keeps its entire state in the URL fragment, which is the only part of it worth treating as 900 a command line. The form is `#/<lat>,<lon>,<zoom>/<YYYY.MM.DD>/<HH:MM>/<object height>`, so a link 901 reproduces a specific reading rather than just opening the tool: 902 903 ```text 904 https://www.suncalc.org/#/52.3738,4.8909,17/2026.07.14/17:40/5/2 905 906 52.3738,4.8909 the coordinate 907 17 map zoom 908 2026.07.14 date, dot-separated 909 17:40 local time at that coordinate, in the timezone the page reports 910 5 object height in metres, which drives the shadow-length readout 911 ``` 912 913 Both assume you have the location right; a 50m error in position barely moves the azimuth, but a 914 wrong date moves it by degrees per week near the solstices. Shadow work narrows a time, it does 915 not prove one — pair it with [image and video forensics](/sheets/osint/image-video-forensics) and 916 with whatever the metadata claims. The full arithmetic, the two-date-window problem and the Python 917 libraries that sweep a whole year are on 918 [Geolocation & Chronolocation](/sheets/osint/geolocation). 919 920 ## Tool reference 921 922 | Tool | What it does | Cost | 923 | --- | --- | --- | 924 | [Apple Maps](https://maps.apple.com) | Apple Maps is a digital mapping service with detailed maps, satellite imagery, and location-based information. | free | 925 | [Baidu Maps](http://map.baidu.com/) | Baidu’s mapping service offering satellite imagery, street maps, and streetview (“Panorama” - zh:百度全景). | free | 926 | [Bellingcat OpenStreetMap Search](https://osm-search.bellingcat.com/) | A user interface to search OpenStreetMap data for features in proximity to each other. | free | 927 | [Bing Maps](https://www.bing.com/maps/) | Bing Maps is a web mapping service provided by Microsoft that offers detailed geographical information and tools for location search, and satellite… | partly free | 928 | [Carte.ma](http://carte.ma/) | Mapping/streetview service for Morocco | free | 929 | [Convert Geographic Units](http://rcn.montana.edu/resources/Converter.aspx) | A tool that converts various geographic coordinates to support diverse mapping and spatial analysis needs. | free | 930 | [Copernicus Browser (formerly Sentinel Hub Playground, EO Browser)](https://browser.dataspace.copernicus.eu/) | A free web-based platform for viewing, analyzing, and downloading satellite imagery from the European Space Agency's Sentinel missions, with data updated… | free | 931 | [EarthExplorer](https://earthexplorer.usgs.gov/) | EarthExplorer is an archive portal from the U.S. Geological Survey (USGS) that allows users search a location and time range to discover and access… | partly free | 932 | [EOS Landviewer](http://eos.com/landviewer) | EOS Landviewer provides free services for up to 10 images. More images and analysis are available to journalists at a discount. Contact: Artem Seredyuk… | paid | 933 | [F4Map](https://demo.f4map.com) | F4Map is an interactive 3D map visualization tool that provides detailed rendering of urban landscapes and geographical features. | free | 934 | [Gaode Maps](https://amap.com) | Gaode Maps (also known as AMap) is a mapping application and technology from the Chinese company Alibaba. | free | 935 | [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 | 936 | [Gjirafa](https://gjirafa.biz/) | Mapping service for Albania (specially Kosovo) | free | 937 | [Global Forest Watch](https://www.globalforestwatch.org/map/) | Explore tree cover loss and gain data, recent deforestation and fire alerts, land use designations, carbon emissions, biodiversity metrics and more. | free | 938 | [Google Earth Engine](https://code.earthengine.google.com/) | Google Earth Engine is a platform for environmental monitoring, land use change and object/infrastructure detection through satellite imagery and… | free | 939 | [Google Earth Pro](https://www.google.com/earth/about/versions/) | Google Earth is a geospatial tool that provides detailed, global satellite imagery, maps, 3D terrain models, and the ability to explore geographic data… | partly free | 940 | [Google Maps](https://www.google.com/maps) | Google Maps provides mapping information, satellite imagery and Google Street View imagery including historical Street View images. | free | 941 | [GovMap](https://www.govmap.gov.il/) | GovMap provides an interactive map of Israel, offering users a wide range of data including property boundaries, planning information, and infrastructure… | free | 942 | [HERE WeGo](https://wego.here.com/) | Mapping service similar to Google Maps or Apple Maps. | free | 943 | [Hitta.se](https://www.hitta.se/) | Mapping service for Sweden | free | 944 | [Index Database](https://www.indexdatabase.de/) | A database which relates remote sensing indices with satellite imaging sensors | free | 945 | [Kakao Map](https://map.kakao.com) | A mapping application provided by South Korean technology company Kakao Corp. | free | 946 | [KartaView](https://kartaview.org/map) | KartaView is a crowdsourced platform for street view imagery. | free | 947 | [Mapa.sk](http://mapa.sk/) | Mapping service for Slovakia | free | 948 | [MapChecking](https://www.mapchecking.com/) | This tool helps you estimate and fact-check the maximum number of people standing in a given area. | free | 949 | [Mapillary](https://www.mapillary.com/) | Mapillary is a crowdsourced street-level imagery platform. | free | 950 | [Mappy](http://en.mappy.com/) | Mapping service (and streetview in a couple of French cities \[double check this!]) | free | 951 | [MapSwitcher](https://github.com/david-r-edgar/MapSwitcher) | Chrome extension switches between online map apps, maintaining (as far as possible) the map centre, zoom level, & directions of the source map. | free | 952 | [mapy.cz](http://mapy.cz) | Mapping service for Czechia | free | 953 | [Maritime Awareness Project](https://map.nbr.org/interactivemap/) | South China Sea maps with oil and gas fields, fishing areas, air defense zones and administrative, claimed, disputed zones, submarine data cables. | free | 954 | [NASA FIRMS](https://firms2.modaps.eosdis.nasa.gov/map/) | Displays a world map overlaid with infra-red data from one or more satellites, some, but not all of which may represent heat from fires and explosions. | free | 955 | [NASA Worldview](https://worldview.earthdata.nasa.gov/) | NASA Worldview is an online tool for visualizing and downloading near real-time satellite imagery and scientific data of Earth's atmosphere, land, and… | free | 956 | [OpenAerialMap](https://openaerialmap.org/) | Platform for accessing open-licensed satellite and unmanned aerial vehicle (UAV) imagery | free | 957 | [OpenInfraMap](https://openinframap.org/#2/26/12) | Power lines, telecoms, solar, oil, gas & water infrastructure mapped globally. | free | 958 | [OpenSeaMap](https://map.openseamap.org/) | Sea map of borders, special zones, shipping lanes, with overlays of MarineTraffic and other sources | free | 959 | [OpenStreetMap](http://openstreetmap.org/) | OpenStreetMap is a collaborative project to create a free editable map of the world. | free | 960 | [OrbTrack](https://www.orbtrack.org) | Predicts & describes the position & path of >15,000 satellites in Earth orbit, relative to points on the earth's surface input by the user, for 5 days… | free | 961 | [Overpass Turbo](https://overpass-turbo.eu/) | Overpass Turbo is a web-based tool for querying and visualizing OpenStreetMap crowd sourced data, aiding in extracting specific information like locations… | free | 962 | [PeakVisor](https://peakvisor.com/) | Dual window views for any global location: (1) a 2-D map & (2) a 3-D rendered terrain model, with photo fitting, shade/slope mapping, sun trails & weather… | free | 963 | [Photo-Map.RU](http://photo-map.ru/) | Geotagged VK posts. | free | 964 | [Planet Labs](https://www.planet.com/) | Planet Labs PBC is an American optical satellite imagery company that sells access to imagery. | partly free | 965 | [QGIS](https://www.qgis.org) | QGIS is a free Open Source Geographic Information System (GIS). | free | 966 | [Quick geolocation search](https://cybdetective.com/quickgeolocationsearch.html) | A tool that brings several maps into one place for easy location search. | free | 967 | [Radar Interference Tracker (RIT)](https://ollielballinger.users.earthengine.app/view/bellingcat-radar-interference-tracker#lon=49.9507;lat=26.6056;zoom=4) | Bellingcat's radar interference tracker can be used to locate and monitor active military radar systems. | free | 968 | [RAMMB SLIDER](https://rammb-slider.cira.colostate.edu/) | Real-time weather satellites of the entire globe | free | 969 | [Satellites.pro](https://satellites.pro/) | Satellites.pro allows open source researchers to quickly switch between several free satellite imagery and mapping services. | free | 970 | [ShadeMap](https://shademap.app) | ShadeMap is a global simulation of mountain, building & tree shadows for a given date & time. Base data is free, but users can buy 30cm accurate data per… | partly free | 971 | [ShadowMap](https://app.shadowmap.org/) | Global map of 3D buildlings and the shadows they cast at a specific time a day | free | 972 | [SkyFi](https://skyfi.com/) | SkyFi is used to purchase commercial satellite imagery and task (order the collection of images) satellites without a subscription. | paid | 973 | [Strava](https://www.strava.com) | A fitness tracking platform where publicly shared GPS activity data can reveal movement patterns, routines, and precise locations of individuals… | partly free | 974 | [Tencent Maps](http://map.qq.com/) | Tencent Maps (formerly SOSO Maps) is a desktop and web mapping service application and technology provided by Chinese company Tencent, offering satellite… | free | 975 | [The European Space Agency (ESA) - Earth Online](https://earth.esa.int/eogateway/tools) | The ESA's Earth Online product offers a portal for accessing satellite imagery and environmental data, supporting a range of applications from climate… | free | 976 | [Topotijdreis.nl](http://topotijdreis.nl) | Over 200 years of maps and topography from the Netherlands. | free | 977 | [Umbra Space](https://umbra.space/) | Umbra is an American synthetic aperture radar (SAR) satellite imaging company that sells on-demand taskings for satellite imagery. | paid | 978 | [UTM grid zones](http://dmap.co.uk/utmworld.htm) | An overview of the Universal Transverse Mercator coordinate system. | free | 979 | [what3words](http://what3words.com/) | A proprietary geocode system which identifies any location on the surface of the earth to a resolution of 3 metres. The identifier is a unique combination… | partly free | 980 | [Wikimapia](https://wikimapia.org/) | Wikimapia is a long-running collaborative mapping project that remains partially accessible, providing open source researchers with a unique database of… | free | 981 | [Yandex Maps](https://yandex.com/maps/) | A platform offering detailed maps, satellite imagery, street views (static & sometimes dynamic imagery, including aerial views). Often the best available… | partly free | 982 | About Maps and Satellites | A guide to using map and satellite tools. | free | 983 984 ## Pitfalls 985 986 - **Undated imagery is useless for a time-sensitive claim.** Record the capture date every time. 987 - **Cloud cover ruins optical revisit rates.** A 5-day nominal revisit can mean a month of usable 988 imagery in the wet season. Radar (Sentinel-1) sees through cloud but is much harder to read. 989 - **A scene's cloud percentage is for the whole tile, not your target.** It is a 100km tile for 990 Sentinel-2 and a 185km swath for Landsat. Use the number to rank candidates and then look at the 991 picture; a 60 per cent scene can be clear over your box and a 12 per cent scene can be solid 992 cloud over it. 993 - **OSM is crowd-sourced.** Completeness varies enormously by region, and an absent feature may 994 simply be unmapped. 995 - **Basemap labels disagree**, particularly on disputed borders and place names. Say which source 996 you used. 997 - **Bounding boxes are in four different orders across these tools.** Overpass takes 998 `south,west,north,east`; Mapillary and STAC take `left,bottom,right,top`; FIRMS takes 999 `west,south,east,north`; and WMS 1.3.0 flips to lat,lon for EPSG:4326 where 1.1.1 does not. An 1000 empty result set is this mistake far more often than it is an absence of data. 1001 - **A median composite has no capture date.** It is a statistic over a window, so it cannot support 1002 a claim about a day. Pick a single scene and name it. 1003 - **Processing levels are not comparable.** L2A against L1C, or Landsat Level-2 against Level-1, 1004 produces a difference image of the atmospheric correction rather than of the ground. 1005 - **Differencing two rasters of different extents aligns them silently.** `gdal_calc.py` without 1006 `--extent=intersect` will give you a delta whose brightest features are registration error. 1007 - **A 10m pixel cannot resolve a 10m object.** Two or three pixels across is the floor for seeing 1008 that something is there; you need an order of magnitude better to say what it is. A roof six 1009 pixels wide is enough to see it disappear and not enough to see how. 1010 - **Street-level coverage is a sample, not a survey.** A junction with 2019 and 2026 frames and 1011 nothing between does not mean nothing happened in between, and the newest frame is not the frame 1012 nearest your date. 1013 - **A thermal detection is an area, not a point.** VIIRS pixels are 375m and MODIS 1km, so plotting 1014 a FIRMS hit as a dot claims a precision the sensor does not have. 1015 - **Near-real-time products get reprocessed.** A FIRMS `_NRT` detection and the later `_SP` version 1016 of the same pass will not agree exactly, so cite the one you can still retrieve. 1017 - **Google's historical slider date is the dominant image's date.** A mosaic blends captures months 1018 apart, and changing zoom can change which image is drawn without the view appearing to move. 1019 1020 ## Worked example 1021 1022 One datum: the coordinate **52.3738, 4.8909**, pulled from a photograph's caption, and a claim 1023 that a warehouse there was demolished in the summer of 2026. 1024 1025 1. **Establish what is mapped.** An Overpass query for `building` ways in a small box around the 1026 coordinate returns three polygons, one tagged `building=warehouse` with an `addr:street`. That 1027 gives the feature a name and an address to search on. The `(newer:"2026-06-01T00:00:00Z")` 1028 form on the same query shows one of the three edited in August 2026, which is a free first 1029 corroboration of the claim's timing from OSM's own history. 1030 2. **Find free imagery either side of the claim.** The Copernicus OData catalogue, filtered to 1031 `SENTINEL-2`, `contains(Name,'MSIL2A')` and intersected with a 1.3km polygon around the point. 1032 Run once per month with a 10 per cent cloud ceiling, and once without, because the gap between 1033 those two answers is the real story: 1034 1035 ```text 1036 === MAY 2026, cloudCover < 10 === 1037 count: 3 1038 2026-05-01T10:36:19 S2B_MSIL2A_20260501T103619_N0512_R008_T31UFU_20260501T143617.SAFE 1039 2026-05-01T10:46:51 S2A_MSIL2A_20260501T104651_N0512_R051_T31UFU_20260501T173800.SAFE 1040 2026-05-26T10:36:21 S2C_MSIL2A_20260526T103621_N0512_R008_T31UFU_20260526T140311.SAFE 1041 1042 === SEPTEMBER 2026, cloudCover < 10 === 1043 count: 0 1044 1045 === SEPTEMBER 2026, cloudCover < 40 === 1046 count: 2 1047 2026-09-01T10:46:19 S2B_MSIL2A_20260901T104619_N0512_R051_T31UFU_20260901T131914.SAFE 1048 2026-09-25T10:40:41 S2A_MSIL2A_20260925T104041_N0513_R008_T31UFU_20260925T171206.SAFE 1049 1050 === SEPTEMBER 2026, no cloud filter === 1051 count: 18 1052 ``` 1053 1054 Eighteen scenes in the month, two under 40 per cent cloud, none under 10. The nominal five-day 1055 revisit is an orbital fact; the usable cadence over the Netherlands in September is a fortnight. 1056 Take the 25 September scene and accept that it needs looking at rather than trusting. 1057 3. **Crop and compare.** `gdal_translate -projwin` cuts both scenes to the same 500m box, 1058 `gdalwarp -t_srs EPSG:3857` puts them in the same CRS, and the pair opened in QGIS shows the 1059 roof present on 1 May and bare ground on 25 September. At 10m the roof is six pixels across — 1060 enough to see it go, not enough to see how. `gdallocationinfo -wgs84 -valonly` on the NDVI 1061 difference at the coordinate returns **+0.02**, confirming what the eye says: this is roof 1062 giving way to bare ground, not vegetation change. 1063 4. **Bracket it with Landsat, for the sun geometry.** The LandsatLook STAC search over the same box 1064 needs no account and returns, for May to September with a 20 per cent cloud ceiling: 1065 1066 ```text 1067 matched: 12 1068 2026-05-28T10:38:56 LC09_L2SP_199023_20260528_20260530_02_T1_SR cloud 4.51 sun_az 153.53 sun_el 56.26 1069 2026-06-22T10:33:16 LC09_L2SP_198024_20260622_20260623_02_T1_SR cloud 7.52 sun_az 148.59 sun_el 58.83 1070 2026-06-29T10:39:07 LC09_L2SP_199023_20260629_20260630_02_T1_SR cloud 6.09 sun_az 150.22 sun_el 57.48 1071 2026-07-15T10:39:14 LC09_L2SP_199023_20260715_20260717_02_T1_SR cloud 0.22 sun_az 150.28 sun_el 55.66 1072 ``` 1073 1074 30m is too coarse to see the building, so this is not the change-detection source here. What it 1075 gives you is `view:sun_elevation` around **55.7 degrees** at 10:39 UTC in mid-July, which is the 1076 number to check the photograph's shadows against in step 7. 1077 5. **Confirm at resolution.** Google Earth Pro's historical slider over the same point has a 1078 **July 2026** frame at sub-metre scale showing partial demolition and plant on site. Record the 1079 status-bar date, not the date you looked, and capture the view as a `LookAt` placemark so the 1080 frame is reproducible rather than described. 1081 6. **Check the ground.** Mapillary `images?bbox=4.8900,52.3730,4.8915,52.3745&start_captured_at=2026-08-01T00:00:00Z` 1082 returns a contributor sequence from August with `computed_compass_angle` 312 degrees, facing the 1083 plot: hoarding up, structure gone. Street level dates the end of the work more precisely than any 1084 satellite pass. The sequence runs north-west along the street, so the frames either side 1085 establish that the hoarding is on the plot boundary and not on the one next door. 1086 7. **Measure, then say so.** Reprojected to UTM zone 31N (`EPSG:32631`), the QGIS measure tool puts 1087 the cleared footprint at **1,840 m²**, against **1,795 m²** for the OSM polygon — a 2.5 per cent 1088 disagreement, which is within what a 10m pixel edge can produce and is therefore consistent 1089 rather than confirming. Quote the CRS alongside the number. 1090 8. **Time the photograph, if it matters.** The caption claims mid-July. SunCalc for the coordinate 1091 on 14 July, with the lamp standard's 5m height entered, returns an azimuth matching the shadow 1092 bearing at around **17:40 local** and a solar elevation of about 30 degrees — consistent with 1093 the Landsat-derived geometry for the same week, and recorded as consistent rather than proven. 1094 1095 What you can assert: a structure present on a named, dated 10m scene on 1 May 2026 and absent from 1096 a named, dated 10m scene on 25 September 2026; a sub-metre Google Earth Pro frame stamped July 2026 1097 showing demolition in progress; a Mapillary sequence from August 2026 showing the site hoarded and 1098 cleared; and a cleared footprint of 1,840 m² in EPSG:32631. The demolition therefore falls between 1099 1 May and 25 September, and the July frame narrows it to the first half of that window. What you 1100 cannot assert: who did it, or why — no imagery source on this page carries that. 1101 1102 What would falsify it: a July Google Earth Pro frame that turns out to be a mosaic blending a 1103 pre-demolition capture from a neighbouring strip, which the visible seam would show and the 1104 status-bar date would not; a Mapillary sequence whose `computed_geometry` is absent, leaving the 1105 position as raw GPS and the "facing the plot" claim unsupported; or a warehouse that was rebuilt 1106 and re-demolished, which two dated frames five months apart cannot distinguish from one event. The 1107 measurement carrying the most risk is the **1,840 m² footprint**. It is traced off 10m pixels, so 1108 each edge carries at best half a pixel of uncertainty: on a roughly 43m square that is about ±5m 1109 per side, or **±8 per cent on the area**. Quote it as 1,840 m² ±150 m² or do not quote a figure at 1110 all — and note that this tolerance is why the 2.5 per cent agreement with the OSM polygon in step 7 1111 corroborates nothing. Two numbers that agree inside their error bars are not a cross-check. 1112 1113 ## Broader catalogues 1114 1115 - [Geolocation and Maps OSINT](https://tools.osintnewsletter.com/tool-categories/geolocation-and-maps-osint) 1116 1117 1118 ## More tools 1119 1120 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. 1121 1122 | Tool | What it does | 1123 | --- | --- | 1124 | [EarthPoint Convert](https://www.earthpoint.us/Convert.aspx) | A web-based conversion tool for transforming geographic coordinate data between formats. | 1125 | [Geoconfirmed](https://geoconfirmed.org/) | A collaborative OSINT platform to help analysts verify and geolocate images, videos, and events from conflicts worldwide. | 1126 | [GeoSpy](https://geospy.ai/) | An AI-powered geolocation tool that analyses images to estimate where they were taken by examining visual features like… | 1127 | [MoonCalc](https://www.mooncalc.org/) | A free web-based lunar positioning tool that visualises the position, phase and illumination of the moon for any location, date… | 1128 | [MW Geofind](https://mattw.io/youtube-geofind/location) | Finds geotagged YouTube videos on a map. | 1129 | [N2YO Satellite Tracker](https://www.n2yo.com/) | Real-time satellite tracking platform providing orbital data, pass predictions and positional information for thousands of… | 1130 | [Open Infrastructure Map](https://openinframap.org/) | An interactive mapping platform that visualises critical infrastructure worldwide using data primarily sourced from OpenStreetMap. | 1131 | [Picarta](https://picarta.ai/) | AI-powered geolocation tool that finds where a photo was taken using visual analysis. | 1132 | [SPOT](https://www.findthatspot.io/) | AI-powered geolocation tool to help identify where an image was taken by analysing visual elements including landmarks, terrain… | 1133 | [SunCalc](https://www.suncalc.org/) | A free web-based geolocation tool that visualises the position of the sun and shadows for any location, date, and time. | 1134 | [Surveillance under Surveillance](https://sunders.uber.space/) | An interactive map that visualises CCTV and surveillance camera locations worldwide using OpenStreetMap data. | 1135 1136 ## Sources 1137 1138 Both catalogues below are maintained by other people and are considerably larger than 1139 this page. Use them as the canonical index; this sheet is a working route through them. 1140 1141 - [Bellingcat's Online Investigation Toolkit](https://bellingcat.gitbook.io/toolkit) — ~340 tools, each with its own 1142 review page covering cost, difficulty, requirements and limitations. 1143 - [OSINT Newsletter Tools Library](https://tools.osintnewsletter.com) — ~280 tools, organised by investigative goal. 1144 1145 Neither publishes a licence, so nothing here is copied from them: tool names, one-line 1146 descriptions, cost flags and links are catalogue facts, and the method and commentary are 1147 this site's own. See [credits](/credits).