Locate the origin point of a photograph using geometric ray intersection.
TracePoint is a browser-based geolocation tool built for OSINT investigators, journalists, and researchers who already know how to geolocate images, and want a dedicated instrument to do it faster and more precisely. No uploads. No server. Everything runs locally in your browser.
Try it live → kluter.github.io/TracePoint
Draw a vertical line across a photograph and it passes through objects at different depths: foreground, midground, horizon. Each of those objects can be found on a map, and together they define a geographic bearing: a ray.
Repeat for a second line and you get a second ray. Where the rays cross is where the photographer was standing.
The more lines you add, the more rays are generated and the more robustly the intersection is averaged. This is a photographic application of the surveying technique known as Resection by Intersection, the same principle used to triangulate a position from known landmarks. TracePoint turns that manual, tab-switching workflow into a single focused tool.
- Split-panel interface: photo left, satellite map right
- Drop any image to load it, or click Browse
- Multi-image session manager: all sessions visible on the map simultaneously, each with its own intersection result
- Bearing display: each line shows its outward bearing once an intersection is found
- Confidence ellipse: with 3 or more lines (up to 5 per image), a dashed ellipse around the intersection visualises the spread of ray crossings. A tighter ellipse means higher confidence. Computed via Eigendecomposition
- Session export and import: save and restore lines, geo points, bearings, and map view as JSON
- EXIF metadata viewer: camera, lens, capture settings, GPS, altitude, and camera direction read from the image. GPS can be placed on the map as a reference marker
- Built-in Potsdam demo with step-by-step guide
- Horizon correction for tilted images
- Map layer switcher: Esri Satellite, OpenStreetMap, Dark Matter, Esri Topo, Esri Streets
- Full keyboard shortcut support
- Fully client-side: no server, no uploads, no tracking
Start with the ☰ menu on the image panel and select Demo — Potsdam for a guided walkthrough. If the photo is tilted, use the Level button first to compensate before adding lines.
- Load a photo: Drop an image onto the left panel, or click Browse. To work on multiple images at once, open the ☰ image menu and click + Add image.
- Add a line: Click + New Line (or press
E) and drag it over a recognisable object you can also find on the map: a building corner, tower, road junction. - Mark points: Switch to Add Point mode (or press
Tab) and click on the line twice to place exactly two reference points. - Place points on the map: Click the 🌐 P1 pill next to each point (or press
Fto jump straight to the next unmapped point), then click its real-world location on the map. - Read the result: Once two lines each have two geo points, rays appear and the intersection is calculated. The pulsing yellow marker shows the estimated camera origin. With 3 or more lines, a confidence ellipse appears around the intersection: the tighter the ellipse, the more consistent your bearings.
- Add more images: Use the ☰ image menu to add further sessions. Each session shows its own rays and intersection on the map simultaneously.
- Save your work: Click the ↓ button on any session row to export it as JSON, or use ↓ Export all sessions to save everything at once. To restore, click ↑ Import session and reload the image when prompted.
Tips: Use objects spread across different depths for a more accurate bearing. Press
Escto deselect or cancel at any time. PressXto toggle the help card from anywhere on the page.
| Action | How |
|---|---|
| Zoom image | Scroll wheel |
| Pan image | Space + drag, or middle-click drag |
| Level image | Level button, then click two points on a horizontal reference |
| Reset level | Click Level button again when correction is active |
| Add a line | + New Line button |
| Drag a line | Click and drag the line |
| Deselect / cancel | Click blank canvas, or Esc |
| Add a point | Add Point mode, click on the line |
| Place point on map | Click the map pill, then click the map |
| Delete a line or point | × button in the line bar |
| Switch map layer | ☰ button, top-right of map panel |
| Manage image sessions | ☰ button, right of line bar |
| Browse for image | Click the drop zone |
| Export session | ↓ button on session row in ☰ menu |
| Export all sessions | ↓ Export all sessions in ☰ menu |
| Import session | ↑ Import session in ☰ menu |
| Open help guide | ? button, top-right of map panel |
| View image metadata | </> button, left of line bar |
| Place EXIF GPS on map | Show on map inside the metadata card |
| Key | Action |
|---|---|
W |
Toggle level tool / reset rotation |
E |
New line |
R |
Delete active line |
F |
Map next unmapped point of active line |
X |
Toggle help card |
Tab |
Toggle Drag Line / Add Point mode |
1 – 5 |
Jump to line 1–5 |
Esc |
Cancel / deselect |
When three or more lines are used, a dashed ellipse appears around the estimated camera position. Its size and shape tell you how much to trust the result.
| Ellipse shape | Meaning | Confidence |
|---|---|---|
| Small and round | Rays converge tightly from all directions | High |
| Small and elongated | Good estimate, but uncertain along one axis. Two lines may be nearly parallel | Moderate |
| Large and round | General spread in all directions, recheck geo points or add more lines | Low |
| Large and elongated | One bearing is likely off, or two lines run too close to parallel | Low |
The ideal result is a small, round ellipse. If you see a large or elongated one, adding a line at a different angle usually improves it significantly.
Rays are computed from the bearing between two geo-referenced points. Each ray extends 50 km in both directions, long enough to contain any realistic intersection while keeping the map readable.
Intersection uses flat-plane geometry on lat/lon coordinates. For most photography this is accurate to within a few metres. The assumption holds well as long as the distance between the camera and the furthest reference landmark stays under roughly 10 km, which covers virtually all real-world OSINT photography. Accuracy degrades in the following cases:
- Very steep mountainous terrain. When reference landmarks sit at dramatically different elevations than the camera, the apparent bearing in the image no longer matches the flat map bearing.
- Aerial photography. Shooting downward at an angle from a plane or drone introduces the same kind of vertical distortion as steep terrain.
- Very long rays. Reference points placed more than 20 to 30 km apart on the map, where the curvature of the Earth starts to affect flat-plane calculations.
- Heavily distorted lenses. Fisheye or extreme wide-angle lenses warp straight lines, so bearing lines drawn on the image no longer correspond accurately to real-world directions.
When three or more lines are used, every pair of rays produces a crossing point. Those crossings form a small cloud around the estimated origin. The confidence ellipse is fitted to that cloud using Eigendecomposition. The ellipse stretches in the direction the crossings are most scattered and stays narrow where they agree. A tight ellipse means the lines converge cleanly; a wide one means at least one bearing is off.
No data leaves your machine. Map imagery is served by public tile servers (Esri, OpenStreetMap). Dependencies are Leaflet for maps and exifr by MikeKovarik for metadata parsing. Everything else is vanilla HTML, CSS, and JavaScript.
npx serve .
# or
python3 -m http.server| Version | Changes |
|---|---|
| v1.6.2 | Fixed map panning to intersection on every geo point placement. |
| v1.6.1 | EXIF GPS popup restyled. Copy button added to GPS. EXIF ray fixed after session import. |
| v1.6.0 | EXIF direction ray: ray from GPS position and camera bearing. Copy coordinates button. |
Older releases
| Version | Changes |
|---|---|
| v1.5.0 | Keyboard shortcuts, confidence ellipse for 3+ lines, 5-line maximum per image. |
| v1.4.3 | Line bar redesign: lines and points move to a dedicated bar below the toolbar. |
| v1.4.2 | UX bug fixes: orphaned map rays, map-point mode not clearing, auto-zoom false trigger. |
| v1.4.1 | Security hardening, JSON import validation, toast notification system. |
| v1.4.0 | EXIF metadata viewer with GPS map marker. |
| v1.3.0 | Session export / import, browse button, bearing display. |
| v1.2.1 | Session naming, colour and map view fixes. |
| v1.2.0 | Multi-image session manager. |
| v1.1.0 | Horizon correction tool. |
| v1.0.0 | Initial release. |
| List | Section | Stars |
|---|---|---|
| jivoi/awesome-osint | Image Analysis | 25k+ |
| Astrosp/Awesome-OSINT-List | GEO | 3.3k |
| The-Osint-Toolbox/Geolocation-OSINT | Geolocation | 313 |
| Publication | Publisher |
|---|---|
| TracePoint: A Browser-Based Tool for Image Geolocation by Ray Intersection | Earth Observation Research Cluster, University of Würzburg |
A polished interface is not the same as a safe one. This section explains exactly what TracePoint does and does not do with your data, so investigators can make an informed decision before using it on sensitive material.
Photos are loaded directly into an HTML5 <canvas> element and processed entirely in memory. No upload, no base64 POST, no fetch call carries image data anywhere. You can verify this yourself:
- Open DevTools → Network tab (
F12) - Load an image into TracePoint
- Filter by Fetch/XHR → no requests involving your image will appear
The only outbound requests are map tile fetches from public CDN servers (Esri, OpenStreetMap). These are standard HTTP requests for map imagery. They contain no session data, no coordinates from your work, and no image content.
// The full extent of network activity in TracePoint:
// GET https://server.arcgisonline.com/ArcGIS/rest/services/.../tile/z/y/x
// GET https://tile.openstreetmap.org/z/x/y.png
// No other requests.TracePoint writes only your chosen map layer to localStorage so it persists between sessions. Nothing else is stored — no image data, no session content, no coordinates. IndexedDB, sessionStorage, and cookies are unused. Closing the tab leaves no investigation trace in the browser.
// Verify in DevTools → Application → Storage
// localStorage: { "tp-tileset": "esri-sat" } ← only your map layer choice
// sessionStorage: empty
// IndexedDB: empty
// Cookies: none set by TracePointExported sessions are standard .json files saved to your local disk. No cloud sync, no server involved. You control where they go and who can access them.
Importing a session does not execute any code. Every field is type-checked and validated on the way in. Unknown or unexpected keys are discarded. String values are sanitised before display. There is no eval(), no raw HTML injection, and no way for a crafted session file to run code or exfiltrate data.
// Example of how imported values are handled:
function escHtml(str) {
return String(str)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
}A manipulated session file cannot execute code, redirect the page, or exfiltrate data. The worst a malformed file can do is fail to import.
TracePoint is vanilla HTML, CSS, and JavaScript with no build step and no obfuscation. Its own logic lives in js/script.js, unminified. What you read in the repo is what runs in your browser. The third-party libraries, Leaflet and exifr, are their standard minified builds loaded from CDN.
This tool was developed with AI assistance. That is disclosed here precisely because the risks of opaque, AI-generated OSINT tools are real. The answer is not to hide the involvement. It is to make the code readable so anyone can verify what it does.
TracePoint has two runtime dependencies:
| Library | Version | Purpose | Touches your data? |
|---|---|---|---|
| Leaflet | 1.9.4 | Map rendering | No. Renders tiles only |
| exifr | latest | EXIF metadata parsing | Locally only. No network calls |
Both are loaded from unpkg.com, a public CDN. This is a trust assumption: if unpkg were compromised, a malicious script could be served in place of either library. Investigators who require full control can clone the repository, replace the CDN links with local copies of both libraries, and run entirely offline:
<!-- Replace in index.html: -->
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
<!-- With local copies: -->
<link rel="stylesheet" href="js/leaflet.css" />
<script src="js/leaflet.js"></script>Then serve locally with npx serve . or python3 -m http.server. No outbound requests except map tiles.
- Map tiles reveal your target location. Tile requests follow the format
tile/z/x/y. The coordinates directly encode the area of the map you are viewing. A tile server operator can infer what location you are investigating. Use a VPN or TOR if that is a concern. - Browser and OS trust. If your browser or operating system is compromised, no web application can protect you. TracePoint is only as secure as the environment it runs in.
- Session files on disk. Exported JSON files are unencrypted plaintext. Treat them with the same care as any other sensitive investigation material.
- CDN dependency. As noted above, Leaflet and exifr are loaded from unpkg by default. Self-hosting both files eliminates this assumption entirely.

