System purpose
geo-sleuth provides Python scripts for an agent to investigate photo locations. The skill organizes candidate locations and evidence records for photos you took or have permission to analyze.
| Input or output | Contents |
|---|---|
| Input | Original photo and known context; hints still require verification |
| Main conclusion | Supported level of the camera location and its uncertainty range |
| Geometry | WGS84 coordinates, error radius, and camera heading when supported by evidence |
| Supporting material | Evidence image, actual commands, and output files |
| Unresolved questions | Alternative candidates, blocked routes, and missing evidence |
Decision
- board.py
- Candidates, evidence, and ranking
Perception
- Preprocessing and lookups
- Map and terrain calculations
- Image similarity ranking
Judgment
- Agent inspects leading candidates
- Checks evidence
- Explains uncertainty
Scripts manage candidates and calculations; the agent judges actual outputs. A ranking does not confirm a location.
This is a skill folder for an agent that can read SKILL.md and execute shell commands. It does not provide a standalone photo geolocation website.
Installation and diagnostics
- Not completed:
Python 3.10 or later.
- Not completed:
Installed
uvandcurl. - Not completed:
An agent that can execute shell commands and view images.
- Not completed:
The complete skill folder, including helper modules.
- Not completed:
Chrome or Playwright Chromium when reverse-image search is needed.
Select your agent in the skills CLI's interactive installer:
npx skills add Oldcircle/geo-sleuthThis installation route requires Node.js/npm; the Python scripts use uv to manage dependencies. To inspect and run the source, use these paths:
git clone https://github.com/Oldcircle/geo-sleuth
cd geo-sleuth
uv run skills/geo-sleuth/scripts/doctor.py --jsonTo check service connectivity, run:
uv run skills/geo-sleuth/scripts/doctor.py --network --jsondoctor.py --network does not upload photos or load OCR or ML models. Dependency installation may still access the network on the first uv run.
If the reverse-search browser is missing, install Chromium with the documented command:
uvx playwright install chromiumCore tools
The cards below name scripts or subcommands. When running from the clone root, prefix each name with uv run skills/geo-sleuth/scripts/; for an installed skill, use its actual path.
Photo preprocessing
intake.py
Prepare initial material
Collect metadata, crops, OCR, and reverse-search results; --no-rev skips the reverse-search branch that uploads photos.
Candidate management
board.py
Record and compare evidence
Manage candidates, clue status, and ranking; read check output instead of relying on its exit code.
Map geometry
osm.py
Build geographic candidates
Query OSM features and linear infrastructure; sparse data does not establish that a real feature is absent.
Ridgeline comparison
terrain.py
Scan terrain and constrain sight lines
Calculate horizons from elevation and compare ridges; a single matching sight line cannot confirm the camera position.
Satellite image ranking
sat_scan.py
Narrow the image inspection scope
Rank grid cells or candidate points with CLIP; the agent still needs to inspect leading thumbnails.
Ground image ranking
match.py
Compare Street View candidates
Use DINOv2 or CLIP, with optional SIFT reranking; a similarity score does not prove a location.
Evidence delivery
evidence.py
Combine maps and comparison images
Draw evidence from existing images and a JSON spec; this does not perform the preceding geolocation calculations.
| Photo clue | Available tools | Verification limit |
|---|---|---|
| Readable text or signs | ocr.py, clues.py, poi.py | Check text found only after enlargement against the original |
| Shadows or sun | sun.py | Verify assumptions about time, mirroring, and camera direction |
| Ridgelines and linear infrastructure | terrain.py, osm.py, geo.py | Another independent geometric constraint is needed to confirm the position |
| Image control points with known locations | pose.py | Feature identification and coordinate quality affect the solution |
| Regional or country clues | regions.py, gazetteer.py | Do not assume a well-known place is the only candidate |
Candidate board and evidence rules
Record clues by status to distinguish hypotheses from measurements:
| Status | Meaning | Use |
|---|---|---|
observed | An observation in the image | Record visible features and keep the crop or original |
read | Text or data that was read | Point to a source file that can be checked |
inferred | A hypothesis drawn from clues | Reinspect the image when hints conflict with metadata |
computed | A script's calculation result | Point to the command actually run and its output |
observed and inferred can change candidate weights but cannot directly justify exclusion. Exclusion must meet the evidence conditions; the existence of a file does not establish that its contents were interpreted correctly.
| Operation | Rule |
|---|---|
| Add candidates | List the full reasonable candidate set; do not select an answer by population or fame |
| Record counterevidence | Downweight first; an unexamined candidate cannot count as excluded |
| Exclude an area or road | The evidence coverage must support the exclusion scope; record covered locations with --covers |
| Refine a location | Preserve candidate containment relationships with --parent |
| Resolve a tie | Use next to choose a shared discriminating test before scheduling a scan |
| Handle discrete candidates | Select the best-supported main candidate and list alternatives; do not average their coordinates |
The board's share is a relative score among the recorded candidates, not a calibrated photo geolocation success rate. Treat final confidence levels separately from candidate ranking.
Operating principles
Check data coverage first
If OSM or Street View has no data, use another justified source. A failed lookup does not prove that a place is absent.
Keep the original resolution
README display images are not the original case inputs. Keep the original and inspect crops; do not use thumbnails to reproduce the author's reported error.
Separate independent evidence
Repeated photos of the same sign do not provide multiple independent supports. Compare candidates using stable terrain or public infrastructure.
Set an investigation budget
Agree on the required level and stopping conditions first. When a service fails, choose a viable route instead of repeatedly sending the same failed request.
On first use, match.py and sat_scan.py install ML dependencies and download weights. The scripts try devices in this order; use GEO_DEVICE to select a supported device:
CUDA.
MPS.
CPU.
Allow time and disk space for model downloads and image processing. Agent costs depend on the host tool and model plan; the MIT code license does not guarantee a cost-free investigation.
Public landscape example
This example uses public-landscape.jpg, a photo you took on a public trail and may lawfully analyze. Set the initial target to area level, and do not locate people who have not consented.
These commands define a workflow example. This report did not analyze a photo. Create a new case from the clone root:
uv run skills/geo-sleuth/scripts/intake.py public-landscape.jpg --out-dir case-landscape/intake --no-rev
uv run skills/geo-sleuth/scripts/board.py --board case-landscape/board.json init --photo public-landscape.jpg --case public-landscapeInspect these outputs first; the initial board contains no geolocation evidence:
| File | Check |
|---|---|
case-landscape/intake/intake.md | Successful, failed, and skipped steps |
case-landscape/intake/intake.json | result and each step's status |
case-landscape/intake/edges/ | Whether crops support the initial observations |
case-landscape/board.json | Empty candidate records initialized for the photo |
Give the following task specification to an agent with the skill loaded:
Analyze public-landscape.jpg, which I took on a public trail. Aim for area-level identification first. Begin with case-landscape/intake and case-landscape/board.json. Ask before uploading the photo to reverse-image search. Record every executed command and its output file. Compare all justified candidates. Do not invent coordinates or precision. Report alternatives, failed routes, and missing evidence if the location remains unresolved.The agent should choose a path from the actual clues. Create case-landscape/spec.json only after confirming the map and geometry:
| Field or dependency | Condition |
|---|---|
map | Path to a tiles.py fetch image; a same-stem JSON georeference file must sit beside it |
camera | Evidence-supported WGS84 camera position |
heading, hfov | Determined by actual measurements or justified camera geometry |
panels | Ground comparison images that can be checked; paths are relative to the spec |
Inspect the map sidecar's failed_tiles and the actual image before composing the evidence image:
uv run skills/geo-sleuth/scripts/evidence.py case-landscape/spec.json --out case-landscape/evidence.jpgThen check the candidate board:
uv run skills/geo-sleuth/scripts/board.py --board case-landscape/board.json check --evidence case-landscape/evidence.jpgIf the command prints FAIL, stop and fix the problem. After the content checks pass, merge the candidate records into the result file:
uv run skills/geo-sleuth/scripts/board.py --board case-landscape/board.json report --merge case-landscape/result.jsonreport --merge organizes candidate records; it does not calculate true coordinates from the photo. If evidence is insufficient, report the supported level and missing information instead of generating a location to fill the fields.
Precision and confidence levels
For an error radius no greater than 100 m, the skill requires the intersection of two independent constraints or ground image agreement on at least three invariant features. This evidence threshold does not replace checks of source quality and camera geometry.
| Report level | High-confidence conditions | Medium-confidence conditions or limits |
|---|---|---|
| City | Evidence such as readable text or a confirmed landmark | Multiple independent weak clues agree |
| Area | At least three overhead features match uniquely, the candidate area has been examined, and no second match remains | The closest match still has unexamined alternatives |
| Road | At least two distinctive ground features match | Layout and one feature agree; without ground imagery, high area confidence and a heading self-check are required |
| Building | Two independent camera position constraints plus at least three matching ground features | Only one of those evidence groups is available |
| Floor | Two types of reference agree | A single reference; omit the floor when confidence is low |
Rate each level separately and report the finest level that reaches at least medium confidence. A single matching ridgeline only constrains a sight line; it cannot establish a camera position with meter-level precision.
The board's --level values differ from the report confidence table. Use levels supported by the CLI when creating candidates; do not pass report labels directly as arguments:
| Supported candidate board levels |
|---|
country, admin1, admin2, city, district, area, road, point |
Limits and precautions
| Condition | Action |
|---|---|
| doctor returns 0 | Read WARN entries and suggested fixes |
| intake returns 0 | It may be partial; inspect the report and per-step status |
| Reverse search returns no results | Check for search failure, CAPTCHA, or service unavailability |
| Black blocks appear on the map | tiles.py fetch may still exit normally; check failed_tiles and do not treat missing imagery as counterevidence |
| Terrain tiles are missing | The default is failure; gaps after --allow-missing cannot count as valid terrain |
| Street View comparison scores are low | Check the viewing angle and imagery date; unrefined does not mean zero matches |
| A large OSM query times out | Narrow the area or change paths; do not repeat the same request |
| Rerunning board init | Handle the existing file separately; do not erase investigation records you need to keep with --force |
Evidence images use a WGS84-aligned basemap. Check the datum before using coordinates read from another map:
| Name | Common use |
|---|---|
| WGS84 | GPS, OSM, and elevation data |
| GCJ-02 | Some Chinese map services |
| BD-09 | Baidu latitude and longitude |
| Baidu Mercator | Projected coordinates for Baidu maps and panorama APIs |
Check code and data licensing separately:
| Item | Terms or action |
|---|---|
| Skill code | MIT |
| Wikipedia-derived lookup data | CC BY-SA 4.0; retain original sources and required attribution |
| Publishing OSM query results | Credit OpenStreetMap contributors as required by ODbL and the source |
| Satellite and Street View imagery | Follow each service's terms; this project's MIT license does not automatically grant unrestricted redistribution rights |
Case directories and .geo-cache/ may retain source information. Inspect file contents before sharing; do not publish unauthorized photos or private locations.
Official cases and evaluation limits
The README lists these reruns by fresh agents. The figures are author-reported and were not reproduced for this report:
| Case | Hint | Author-reported distance | Time |
|---|---|---|---|
| Desert ridgeline | In China | 87 m from the confirmed camera position | 32 min |
| Vancouver cherry blossom street | None | 3 m from the confirmed camera position | 53 min |
Both photos had previously been solved, and the resulting lessons were incorporated into the skill. The README therefore treats them as regression checks. They do not establish overall accuracy on unseen photos; a public blind-test set of unseen photos remains on the roadmap.
An individual operator's retrieval ranking does not establish end-to-end geolocation success. When evaluating your own use case, retain unresolved cases and actual investigation costs.
Next actions
| Next task | Action |
|---|---|
| Service failure | Rerun the relevant doctor diagnostics and record failed routes |
| Change model device | Check GEO_DEVICE and the current torch build's support |
| New country data | Review region packs and lookup sources before deciding the supported scope |
| Report an error | Provide the first step that diverged and the minimum checkable data; do not post private homes or personally identifiable material |
Current tool messages and documentation are primarily in English. OCR and source text retain their original language; the agent explains them in the reader's language. The frozen zh-final tag does not replace the current mainline documentation.
This manual reviews revision 1a9ecee153b02e2db2e62352002fa5b17bbd819d; the skill document is labeled v2. This report did not perform:
Photo geolocation or case reruns.
Photo uploads or map service queries.
Skill installation or model downloads.