Metadata-Version: 2.4
Name: geocsv
Version: 0.1.0
Summary: Turn any spreadsheet or CSV of places into a map-ready GeoJSON, GeoPackage, KML or GPX file, without GDAL.
Author-email: Yusuf Eminoğlu <yusufeminoglu@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/YusufEminoglu/geocsv_sdk
Project-URL: Repository, https://github.com/YusufEminoglu/geocsv_sdk
Project-URL: Issues, https://github.com/YusufEminoglu/geocsv_sdk/issues
Project-URL: Changelog, https://github.com/YusufEminoglu/geocsv_sdk/blob/main/CHANGELOG.md
Keywords: gis,csv,geojson,geopackage,gpkg,kml,gpx,wkt,latitude,longitude,geocoding,dms,geohash,plus-codes,open-location-code,mgrs,utm,maidenhead,spreadsheet,data-cleaning
Classifier: Development Status :: 4 - Beta
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Operating System :: OS Independent
Classifier: Topic :: Scientific/Engineering :: GIS
Classifier: Topic :: Text Processing
Classifier: Topic :: Utilities
Classifier: Environment :: Console
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: End Users/Desktop
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: pyproj
Requires-Dist: pyproj>=3.3.0; extra == "pyproj"
Provides-Extra: shapefile
Requires-Dist: pyshp>=2.3.0; extra == "shapefile"
Provides-Extra: dev
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
Requires-Dist: ruff>=0.1.0; extra == "dev"
Requires-Dist: mypy>=1.0.0; extra == "dev"
Dynamic: license-file

<div align="center">

# geocsv

[![PyPI version](https://img.shields.io/pypi/v/geocsv.svg?color=10b981)](https://pypi.org/project/geocsv/)
[![Python version support](https://img.shields.io/pypi/pyversions/geocsv.svg?color=3b82f6)](https://pypi.org/project/geocsv/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/YusufEminoglu/geocsv_sdk/blob/main/LICENSE)
[![Code style: Ruff](https://img.shields.io/badge/code%20style-ruff-D7FF64.svg)](https://docs.astral.sh/ruff/)

**Turn any spreadsheet or CSV of places into a map-ready GeoJSON, GeoPackage, KML or GPX file, without GDAL.**

[📦 PyPI](https://pypi.org/project/geocsv/) • [💻 GitHub](https://github.com/YusufEminoglu/geocsv_sdk) • [🐛 Issues](https://github.com/YusufEminoglu/geocsv_sdk/issues)

</div>

You have a spreadsheet export with a column called `Breitengrad`, or `enlem;boylam`
with decimal commas, or `40°26′46″N 79°58′56″W` strings, or Plus Codes, or MGRS
references. geocsv finds the coordinates, tells you how sure it is and why, reports the
rows that are broken, and writes a file that QGIS, ArcGIS, Google Earth, a GPS or a web
map can open. It is pure Python with **no required dependencies**.

## 📦 Installation

```bash
pip install geocsv

# optional extras
pip install "geocsv[pyproj]"     # reproject x/y or EWKT from any EPSG code
pip install "geocsv[shapefile]"  # write ESRI Shapefiles (via pyshp)
```

Python 3.9+ on Windows, macOS or Linux.

## ⚡ Quickstart

```bash
geocsv inspect places.csv                 # what was detected, and which rows are wrong
geocsv convert places.csv -o places.gpkg  # GeoPackage (also .geojson .geojsonl .kml .gpx .shp)
geocsv convert places.csv -o places.geojson --lat "Y koord" --lon "X koord"
```

```python
import geocsv

table = geocsv.read("places.csv")
print(table.detection.describe())
# latlon: lat='Breitengrad', lon='Längengrad', decimal (confidence 0.99)
print(table.report())

table.to_gpkg("places.gpkg")
table.to_geojson("places.geojson")
fc = table.to_geojson()          # or get the FeatureCollection as a dict
table.to_kml("places.kml")
table.to_gpx("places.gpx")

geocsv.detect("places.csv")      # detection only, with .reasons and .alternatives
```

## 🔎 What it reads

**CSV dialects** (standard library only)

* encodings: UTF-8, UTF-8 with BOM, UTF-16 (Excel "Unicode text"), and the Windows code
  pages cp1252 / latin-1 / cp1254 (Turkish letters such as ğ ı ş are told apart from
  Western European text)
* delimiters `,` `;` tab `|` (sniffed from the data, quoted fields respected) and Excel's
  `sep=;` first line
* decimal commas (`40,7128`), header or no header (detected), ragged rows

**Geometry encodings**, found automatically:

| kind | example | notes |
|---|---|---|
| `latlon` | `lat` / `lon` columns | header names in many languages: lat, latitude, latitud, Breitengrad, enlem, широта, lng, long, Längengrad, boylam, долгота, … plus value ranges when names are missing |
| `latlon` (DMS) | `40°26′46″N`, `N40 26.767`, `40d26m46s` | degrees, minutes, seconds, any symbols or letters |
| `pair` | `"41.0082, 28.9784"`, `40°26′46″N 79°58′56″W` | one column; lat/lon order inferred from ranges, hemisphere letters or a `lonlat` header |
| `wkt` | `POINT (29 41)`, `SRID=3857;POLYGON ((…))` | all simple-feature types, Z/M/ZM, EWKT |
| `geojson` | `{"type": "Point", "coordinates": [29, 41]}` | geometry or Feature objects |
| `geohash` | `sxk97cn4` | |
| `pluscode` | `8GFJWVM5+9V` | Open Location Code; short codes with `plus_code_reference` |
| `maidenhead` | `KM69kw` | 2-10 characters |
| `mgrs` | `35T PF 66370 41552` | WGS 84, pure-Python Krüger series |
| `utm` | `35T 666371 4541552` | zone + band or hemisphere |
| `xy` | `easting` / `northing` columns | any EPSG code via the `pyproj` extra |

Every detection has a confidence and human-readable reasons:

```text
$ geocsv detect stations.csv
latlon: lat='lon', lon='lat', decimal (confidence 0.99)
  - header 'lat' looks like latitude but its values only fit longitude; the columns are swapped
  - 100% of values are coordinates; 100% are within latitude/longitude range
  - the two columns are adjacent
```

Override any decision: `lat=`/`lon=`, `wkt=`, `column=` + `kind=`, `x=`/`y=` + `epsg=`,
`order="lonlat"`, `encoding=`, `delimiter=`, `header=` (CLI: `--lat`, `--lon`, `--wkt`,
`--column`, `--kind`, `--x`, `--y`, `--epsg`, `--order`, `--encoding`, `--delimiter`,
`--header/--no-header`). Columns can be named or numbered from 1.

## 🩺 Validation report

```text
$ geocsv inspect issues.csv
Geometry   latlon: lat='latitude', lon='longitude', decimal (confidence 0.87)
Valid      8 of 13 rows  bbox=(0.000000, 0.000000, 37.874600, 41.008200)

issue           severity   rows  lines
--------------  --------  -----  --------------------
missing         error         2  3, 4
invalid         error         1  5
out_of_range    error         1  6
swapped         error         1  9
likely_swapped  warning       1  13
null_island     warning       1  7
duplicate       warning       1  8
ragged          warning       1  14
```

| code | severity | meaning |
|---|---|---|
| `missing` | error | empty geometry cell(s) |
| `invalid` | error | the value does not parse |
| `empty` | error | WKT/GeoJSON `EMPTY` geometry |
| `out_of_range` | error | outside ±90 / ±180 |
| `swapped` | error | latitude beyond ±90 but would fit if swapped (`fix_swapped=True` repairs it) |
| `crs` | error | EWKT in another CRS and `pyproj` is not installed |
| `likely_swapped` | warning | in range, but far closer to the rest of the table when swapped |
| `swapped_fixed` | warning | swapped back (by `fix_swapped=True` or hemisphere letters) |
| `null_island` | warning | exactly (0, 0) |
| `duplicate` | warning | same geometry as an earlier row |
| `ragged` | warning | row length differs from the header |

Line numbers are physical lines in the file, as your editor shows them.
`table.issues`, `table.summary()` and `geocsv inspect --json` give the same data in
machine-readable form; `geocsv convert --strict` exits with status 1 when any row has an
error.

## 🗺️ What it writes

| format | method | notes |
|---|---|---|
| GeoJSON | `to_geojson(path=None)` | RFC 7946 FeatureCollection; returns a dict without a path |
| GeoJSONSeq | `to_geojsonseq(path)` | one Feature per line (`.geojsonl`, `.ndjson`) |
| GeoPackage | `to_gpkg(path, layer=None)` | pure `sqlite3`; OGC GeoPackage 1.4 tables, StandardGeoPackageBinary + ISO WKB, any geometry type, Z |
| KML | `to_kml(path=None)` | Placemarks with ExtendedData, holes, MultiGeometry |
| GPX | `to_gpx(path=None)` | points as waypoints, lines as tracks |
| Shapefile | `to_shapefile(path)` | needs `geocsv[shapefile]`; one geometry family |
| any | `to_file(path, driver=None)` | picks the format from the extension |

Attributes keep their types: integer, real or text (codes like `"00123"` stay text).
A `name`-like column (`name`, `title`, `isim`, `nombre`, …) labels KML placemarks and GPX
waypoints. The GeoPackage writer is tested against `sqlite3`, GDAL's `ogrinfo`, GDAL's
`validate_gpkg.py` conformance checker and GeoPandas.

## 🧭 Coordinate helpers

The decoders are public and dependency-free:

```python
from geocsv import dms, geohash, pluscode, maidenhead, utm

dms.parse_dms_pair("40°26′46″N 79°58′56″W")   # (40.4461, -79.9822)
dms.format_dms(40.446111, "lat")             # '40°26′46.0″N'
geohash.decode("u4pruydqqvj")                # (57.64911, 10.40744)
pluscode.decode("849VCWC8+R9")               # (37.4220625, -122.0840625)
pluscode.recover_nearest("CWC8+R9", 37.4, -122.0)
maidenhead.decode("FN31pr")                  # (41.7292, -72.7083)
utm.mgrs_to_latlon("31U DQ 48251 11932")     # (48.8582, 2.2945)
utm.latlon_to_mgrs(48.8582, 2.2945)          # '31U DQ 48251 11932'
utm.utm_to_latlon(31, "N", 448251, 5411932)
```

UTM/MGRS use Krüger's series to sixth order; they agree with PROJ to under a millimetre.

## 🛠️ Development

```bash
git clone https://github.com/YusufEminoglu/geocsv_sdk.git
cd geocsv_sdk
pip install -e ".[dev,pyproj,shapefile]"
python -m pytest --cov=geocsv
python -m ruff check . && python -m ruff format --check src tests && python -m mypy src
```

The tests include hand-made fixture CSVs, reference values for every decoder, a seeded
synthetic corpus of 400 tables that measures detection accuracy, and GDAL-based checks
of the GeoPackage output that run when GDAL is installed.

## 📄 License

MIT © Yusuf Eminoğlu
