pontos.geo¶
Geospatial utilities for coordinate transformation and GeoJSON export.
GeoExporter¶
Static class for converting pixel-space detections to geographic coordinates and exporting to GeoJSON.
Class Definition¶
class GeoExporter:
"""
Geospatial export utilities for vessel detections.
Converts pixel coordinates to geographic coordinates (WGS84)
and exports results as GeoJSON FeatureCollections.
All methods are static - no instantiation required.
Example:
>>> GeoExporter.detections_to_geojson(
... detections, bbox, image_size, "vessels.geojson"
... )
"""
Methods¶
detections_to_geojson() (static)¶
Convert pixel-space detections to a GeoJSON FeatureCollection.
@staticmethod
def detections_to_geojson(
detections: list[dict],
bbox: tuple[float, float, float, float],
image_size: tuple[int, int],
output_path: str | Path
) -> Path
Parameters:
| Parameter | Type | Description |
|---|---|---|
detections |
list[dict] |
List of detection dictionaries with center key |
bbox |
tuple |
Geographic bounding box (min_lon, min_lat, max_lon, max_lat) |
image_size |
tuple |
Image dimensions as (width, height) in pixels |
output_path |
str or Path |
Output file path for GeoJSON |
Returns:
Path - Path to the saved GeoJSON file.
Example:
from pontos import GeoExporter
detections = [
{"center": [512, 384], "confidence": 0.87, "class": "vessel"},
{"center": [256, 128], "confidence": 0.65, "class": "vessel"}
]
geojson_path = GeoExporter.detections_to_geojson(
detections=detections,
bbox=(5.85, 43.08, 6.05, 43.18),
image_size=(1024, 1024),
output_path="vessels.geojson"
)
_pixel_to_geo() (private static)¶
Convert pixel coordinates to geographic coordinates.
@staticmethod
def _pixel_to_geo(
x_px: float,
y_px: float,
bbox: tuple[float, float, float, float],
image_size: tuple[int, int]
) -> tuple[float, float]
Parameters:
| Parameter | Type | Description |
|---|---|---|
x_px |
float |
X coordinate in pixels |
y_px |
float |
Y coordinate in pixels |
bbox |
tuple |
Geographic bounding box |
image_size |
tuple |
Image dimensions (width, height) |
Returns:
tuple[float, float] - Geographic coordinates as (longitude, latitude).
Transformation Formula:
lon = min_lon + (x_px / width) * (max_lon - min_lon)
lat = max_lat - (y_px / height) * (max_lat - min_lat)
Coordinate System
The Y-axis is inverted because image coordinates have origin at top-left, while geographic coordinates have latitude increasing northward.
GeoJSON Output Format¶
FeatureCollection Structure¶
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [longitude, latitude]
},
"properties": {
"id": 0,
"confidence": 0.87,
"class": "vessel"
}
}
]
}
Feature Properties¶
| Property | Type | Description |
|---|---|---|
id |
int |
Sequential detection ID |
confidence |
float |
Detection confidence (0.0-1.0) |
class |
str |
Object class name |
Coordinate Transformation¶
Pixel to Geographic¶
The transformation maps pixel coordinates to WGS84 coordinates:
Image Space (pixels) Geographic Space (WGS84)
max_lat (north)
(0,0) ──────────► X │
│ min_lon ─────── max_lon
│ (west) (east)
▼ │
Y min_lat (south)
(w,h)
Transformation Details¶
- X-axis mapping: Linear interpolation from
min_lontomax_lon - Y-axis mapping: Inverted linear interpolation from
max_lattomin_lat
# Example transformation
image_size = (1024, 1024)
bbox = (5.85, 43.08, 6.05, 43.18) # min_lon, min_lat, max_lon, max_lat
# Pixel (512, 512) -> center of image
x_px, y_px = 512, 512
# Calculate geographic coordinates
lon = 5.85 + (512 / 1024) * (6.05 - 5.85) # = 5.95
lat = 43.18 - (512 / 1024) * (43.18 - 43.08) # = 43.13
# Result: (5.95, 43.13)
Usage Examples¶
Basic Export¶
from pontos import GeoExporter
# Sample detections from VesselDetector
detections = [
{
"bbox": [100, 100, 200, 200],
"confidence": 0.87,
"class": "vessel",
"center": [150, 150]
},
{
"bbox": [500, 300, 600, 400],
"confidence": 0.65,
"class": "vessel",
"center": [550, 350]
}
]
# Export to GeoJSON
GeoExporter.detections_to_geojson(
detections=detections,
bbox=(5.85, 43.08, 6.05, 43.18),
image_size=(1024, 1024),
output_path="vessels.geojson"
)
Full Pipeline¶
from pontos import SentinelDataSource, VesselDetector, GeoExporter
from pathlib import Path
# Define area
bbox = (5.85, 43.08, 6.05, 43.18)
image_size = (1024, 1024)
# Download scene
sentinel = SentinelDataSource()
scene = sentinel.get_scene(
bbox=bbox,
time_range=("2026-01-01", "2026-01-31"),
size=image_size[0]
)
# Detect vessels
detector = VesselDetector()
detections = detector.detect(scene)
# Export to GeoJSON
output_path = GeoExporter.detections_to_geojson(
detections=detections,
bbox=bbox,
image_size=image_size,
output_path=Path("output/vessels.geojson")
)
print(f"Exported {len(detections)} vessels to {output_path}")
Load and Visualize¶
import json
import folium
# Load GeoJSON
with open("vessels.geojson") as f:
geojson = json.load(f)
# Create map
center = [43.13, 5.95]
m = folium.Map(location=center, zoom_start=12)
# Add features
for feature in geojson["features"]:
coords = feature["geometry"]["coordinates"]
props = feature["properties"]
folium.Marker(
location=[coords[1], coords[0]], # [lat, lon]
popup=f"Confidence: {props['confidence']:.2%}"
).add_to(m)
m.save("vessels_map.html")
Integration with GIS Tools¶
QGIS¶
- Layer > Add Layer > Add Vector Layer
- Select
vessels.geojson - Add a basemap for context
geojson.io¶
Drag and drop the file at geojson.io
Python Libraries¶
import geopandas as gpd
# Load as GeoDataFrame
gdf = gpd.read_file("vessels.geojson")
print(gdf.head())
print(f"Total vessels: {len(gdf)}")
print(f"CRS: {gdf.crs}")
# Filter high-confidence
high_conf = gdf[gdf["confidence"] > 0.8]
Error Handling¶
from pontos import GeoExporter
from pathlib import Path
try:
GeoExporter.detections_to_geojson(
detections=[], # Empty list
bbox=(5.85, 43.08, 6.05, 43.18),
image_size=(1024, 1024),
output_path="empty.geojson"
)
# Still creates valid GeoJSON with empty features array
except Exception as e:
print(f"Export failed: {e}")
Notes¶
- All coordinates are in WGS84 (EPSG:4326)
- GeoJSON follows the RFC 7946 specification
- Point geometries are used (vessel center points)
- Output directory is created automatically if it doesn't exist