geocsv 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. geocsv-0.1.0/CHANGELOG.md +26 -0
  2. geocsv-0.1.0/CITATION.cff +20 -0
  3. geocsv-0.1.0/LICENSE +21 -0
  4. geocsv-0.1.0/MANIFEST.in +9 -0
  5. geocsv-0.1.0/PKG-INFO +233 -0
  6. geocsv-0.1.0/README.md +192 -0
  7. geocsv-0.1.0/examples/quickstart.py +35 -0
  8. geocsv-0.1.0/pyproject.toml +133 -0
  9. geocsv-0.1.0/setup.cfg +4 -0
  10. geocsv-0.1.0/src/geocsv/__init__.py +57 -0
  11. geocsv-0.1.0/src/geocsv/__main__.py +7 -0
  12. geocsv-0.1.0/src/geocsv/cli.py +202 -0
  13. geocsv-0.1.0/src/geocsv/detect.py +477 -0
  14. geocsv-0.1.0/src/geocsv/dms.py +260 -0
  15. geocsv-0.1.0/src/geocsv/errors.py +25 -0
  16. geocsv-0.1.0/src/geocsv/geohash.py +91 -0
  17. geocsv-0.1.0/src/geocsv/geometry.py +167 -0
  18. geocsv-0.1.0/src/geocsv/gpkg.py +229 -0
  19. geocsv-0.1.0/src/geocsv/maidenhead.py +75 -0
  20. geocsv-0.1.0/src/geocsv/names.py +165 -0
  21. geocsv-0.1.0/src/geocsv/parsers.py +177 -0
  22. geocsv-0.1.0/src/geocsv/pluscode.py +175 -0
  23. geocsv-0.1.0/src/geocsv/py.typed +0 -0
  24. geocsv-0.1.0/src/geocsv/shapefile_writer.py +112 -0
  25. geocsv-0.1.0/src/geocsv/table.py +911 -0
  26. geocsv-0.1.0/src/geocsv/textio.py +247 -0
  27. geocsv-0.1.0/src/geocsv/utm.py +305 -0
  28. geocsv-0.1.0/src/geocsv/wkt.py +347 -0
  29. geocsv-0.1.0/src/geocsv/writers.py +214 -0
  30. geocsv-0.1.0/src/geocsv.egg-info/PKG-INFO +233 -0
  31. geocsv-0.1.0/src/geocsv.egg-info/SOURCES.txt +61 -0
  32. geocsv-0.1.0/src/geocsv.egg-info/dependency_links.txt +1 -0
  33. geocsv-0.1.0/src/geocsv.egg-info/entry_points.txt +2 -0
  34. geocsv-0.1.0/src/geocsv.egg-info/requires.txt +12 -0
  35. geocsv-0.1.0/src/geocsv.egg-info/top_level.txt +1 -0
  36. geocsv-0.1.0/tests/conftest.py +47 -0
  37. geocsv-0.1.0/tests/data/dms_columns.csv +6 -0
  38. geocsv-0.1.0/tests/data/dms_pair.csv +6 -0
  39. geocsv-0.1.0/tests/data/excel_sep.csv +5 -0
  40. geocsv-0.1.0/tests/data/geohash.csv +6 -0
  41. geocsv-0.1.0/tests/data/geojson_column.csv +5 -0
  42. geocsv-0.1.0/tests/data/german.tsv +5 -0
  43. geocsv-0.1.0/tests/data/issues.csv +14 -0
  44. geocsv-0.1.0/tests/data/latlon_basic.csv +9 -0
  45. geocsv-0.1.0/tests/data/lonlat_pair.csv +5 -0
  46. geocsv-0.1.0/tests/data/maidenhead.csv +5 -0
  47. geocsv-0.1.0/tests/data/mgrs.csv +5 -0
  48. geocsv-0.1.0/tests/data/no_header.csv +4 -0
  49. geocsv-0.1.0/tests/data/pair_column.csv +6 -0
  50. geocsv-0.1.0/tests/data/pluscode.csv +5 -0
  51. geocsv-0.1.0/tests/data/projected_xy.csv +4 -0
  52. geocsv-0.1.0/tests/data/swapped_columns.csv +7 -0
  53. geocsv-0.1.0/tests/data/utm.csv +5 -0
  54. geocsv-0.1.0/tests/data/values_only.csv +5 -0
  55. geocsv-0.1.0/tests/data/wkt.csv +9 -0
  56. geocsv-0.1.0/tests/test_cli.py +164 -0
  57. geocsv-0.1.0/tests/test_decoders.py +286 -0
  58. geocsv-0.1.0/tests/test_detect.py +265 -0
  59. geocsv-0.1.0/tests/test_dms.py +120 -0
  60. geocsv-0.1.0/tests/test_table.py +314 -0
  61. geocsv-0.1.0/tests/test_textio.py +133 -0
  62. geocsv-0.1.0/tests/test_wkt.py +277 -0
  63. geocsv-0.1.0/tests/test_writers.py +325 -0
@@ -0,0 +1,26 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [Unreleased]
6
+
7
+ ## [0.1.0] - 2026-11-08
8
+ ### Added
9
+ - `geocsv.read()` / `read_string()` return a `GeoTable` with detected geometry, typed
10
+ attributes (integer, real, text) and per-row issues; `geocsv.detect()` returns the
11
+ detection alone.
12
+ - CSV handling with the standard library only: encoding detection (UTF-8, UTF-8 BOM,
13
+ UTF-16, cp1252, latin-1, cp1254), delimiter sniffing (`,` `;` tab `|`), Excel's
14
+ `sep=` line, decimal commas, quoted fields and header detection.
15
+ - Geometry detection with a confidence score and reasons: latitude/longitude columns by
16
+ header names in many languages or by value ranges, swapped-column detection, DMS
17
+ values, single-column coordinate pairs, WKT/EWKT, GeoJSON, geohash, Plus Codes
18
+ (including short codes with a reference point), Maidenhead locators, MGRS, UTM and
19
+ projected x/y with an EPSG code (`pyproj` extra).
20
+ - Pure-Python decoders: DMS parser and formatter, geohash, Open Location Code,
21
+ Maidenhead, UTM and MGRS (Krüger series, WGS 84).
22
+ - Validation: missing, invalid, empty, out-of-range, swapped and likely-swapped rows,
23
+ (0, 0) "null island", duplicates and ragged rows, with a text report and JSON summary.
24
+ - Writers: GeoJSON, GeoJSONSeq, KML, GPX and an OGC GeoPackage written with `sqlite3`
25
+ only; ESRI Shapefile through the `shapefile` extra.
26
+ - `geocsv` command with `inspect`, `detect` and `convert` sub-commands.
@@ -0,0 +1,20 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software, please cite it as below."
3
+ authors:
4
+ - family-names: "Eminoğlu"
5
+ given-names: "Yusuf"
6
+ email: "yusufeminoglu@gmail.com"
7
+ orcid: "https://orcid.org/0009-0005-6000-2934"
8
+ title: "geocsv"
9
+ version: "0.1.0"
10
+ date-released: "2026-11-08"
11
+ license: "MIT"
12
+ repository-code: "https://github.com/YusufEminoglu/geocsv_sdk"
13
+ url: "https://pypi.org/project/geocsv/"
14
+ abstract: "Pure-Python reader that finds the coordinates in any CSV or spreadsheet export (lat/lon columns, DMS, WKT, GeoJSON, geohash, Plus Codes, Maidenhead, MGRS, UTM), validates them and writes GeoJSON, GeoJSONSeq, KML, GPX or GeoPackage without GDAL."
15
+ keywords:
16
+ - gis
17
+ - csv
18
+ - geojson
19
+ - geopackage
20
+ - coordinates
geocsv-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yusuf Eminoğlu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,9 @@
1
+ include LICENSE
2
+ include README.md
3
+ include CHANGELOG.md
4
+ include CITATION.cff
5
+ include pyproject.toml
6
+ include src/geocsv/py.typed
7
+ recursive-include src/geocsv *.py
8
+ recursive-include tests *.py *.csv *.tsv *.txt
9
+ recursive-include examples *.py *.csv
geocsv-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,233 @@
1
+ Metadata-Version: 2.4
2
+ Name: geocsv
3
+ Version: 0.1.0
4
+ Summary: Turn any spreadsheet or CSV of places into a map-ready GeoJSON, GeoPackage, KML or GPX file, without GDAL.
5
+ Author-email: Yusuf Eminoğlu <yusufeminoglu@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/YusufEminoglu/geocsv_sdk
8
+ Project-URL: Repository, https://github.com/YusufEminoglu/geocsv_sdk
9
+ Project-URL: Issues, https://github.com/YusufEminoglu/geocsv_sdk/issues
10
+ Project-URL: Changelog, https://github.com/YusufEminoglu/geocsv_sdk/blob/main/CHANGELOG.md
11
+ 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
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Topic :: Scientific/Engineering :: GIS
21
+ Classifier: Topic :: Text Processing
22
+ Classifier: Topic :: Utilities
23
+ Classifier: Environment :: Console
24
+ Classifier: Intended Audience :: Science/Research
25
+ Classifier: Intended Audience :: Developers
26
+ Classifier: Intended Audience :: End Users/Desktop
27
+ Classifier: Typing :: Typed
28
+ Requires-Python: >=3.9
29
+ Description-Content-Type: text/markdown
30
+ License-File: LICENSE
31
+ Provides-Extra: pyproj
32
+ Requires-Dist: pyproj>=3.3.0; extra == "pyproj"
33
+ Provides-Extra: shapefile
34
+ Requires-Dist: pyshp>=2.3.0; extra == "shapefile"
35
+ Provides-Extra: dev
36
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
37
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
38
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
39
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
40
+ Dynamic: license-file
41
+
42
+ <div align="center">
43
+
44
+ # geocsv
45
+
46
+ [![PyPI version](https://img.shields.io/pypi/v/geocsv.svg?color=10b981)](https://pypi.org/project/geocsv/)
47
+ [![Python version support](https://img.shields.io/pypi/pyversions/geocsv.svg?color=3b82f6)](https://pypi.org/project/geocsv/)
48
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/YusufEminoglu/geocsv_sdk/blob/main/LICENSE)
49
+ [![Code style: Ruff](https://img.shields.io/badge/code%20style-ruff-D7FF64.svg)](https://docs.astral.sh/ruff/)
50
+
51
+ **Turn any spreadsheet or CSV of places into a map-ready GeoJSON, GeoPackage, KML or GPX file, without GDAL.**
52
+
53
+ [📦 PyPI](https://pypi.org/project/geocsv/) • [💻 GitHub](https://github.com/YusufEminoglu/geocsv_sdk) • [🐛 Issues](https://github.com/YusufEminoglu/geocsv_sdk/issues)
54
+
55
+ </div>
56
+
57
+ You have a spreadsheet export with a column called `Breitengrad`, or `enlem;boylam`
58
+ with decimal commas, or `40°26′46″N 79°58′56″W` strings, or Plus Codes, or MGRS
59
+ references. geocsv finds the coordinates, tells you how sure it is and why, reports the
60
+ rows that are broken, and writes a file that QGIS, ArcGIS, Google Earth, a GPS or a web
61
+ map can open. It is pure Python with **no required dependencies**.
62
+
63
+ ## 📦 Installation
64
+
65
+ ```bash
66
+ pip install geocsv
67
+
68
+ # optional extras
69
+ pip install "geocsv[pyproj]" # reproject x/y or EWKT from any EPSG code
70
+ pip install "geocsv[shapefile]" # write ESRI Shapefiles (via pyshp)
71
+ ```
72
+
73
+ Python 3.9+ on Windows, macOS or Linux.
74
+
75
+ ## ⚡ Quickstart
76
+
77
+ ```bash
78
+ geocsv inspect places.csv # what was detected, and which rows are wrong
79
+ geocsv convert places.csv -o places.gpkg # GeoPackage (also .geojson .geojsonl .kml .gpx .shp)
80
+ geocsv convert places.csv -o places.geojson --lat "Y koord" --lon "X koord"
81
+ ```
82
+
83
+ ```python
84
+ import geocsv
85
+
86
+ table = geocsv.read("places.csv")
87
+ print(table.detection.describe())
88
+ # latlon: lat='Breitengrad', lon='Längengrad', decimal (confidence 0.99)
89
+ print(table.report())
90
+
91
+ table.to_gpkg("places.gpkg")
92
+ table.to_geojson("places.geojson")
93
+ fc = table.to_geojson() # or get the FeatureCollection as a dict
94
+ table.to_kml("places.kml")
95
+ table.to_gpx("places.gpx")
96
+
97
+ geocsv.detect("places.csv") # detection only, with .reasons and .alternatives
98
+ ```
99
+
100
+ ## 🔎 What it reads
101
+
102
+ **CSV dialects** (standard library only)
103
+
104
+ * encodings: UTF-8, UTF-8 with BOM, UTF-16 (Excel "Unicode text"), and the Windows code
105
+ pages cp1252 / latin-1 / cp1254 (Turkish letters such as ğ ı ş are told apart from
106
+ Western European text)
107
+ * delimiters `,` `;` tab `|` (sniffed from the data, quoted fields respected) and Excel's
108
+ `sep=;` first line
109
+ * decimal commas (`40,7128`), header or no header (detected), ragged rows
110
+
111
+ **Geometry encodings**, found automatically:
112
+
113
+ | kind | example | notes |
114
+ |---|---|---|
115
+ | `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 |
116
+ | `latlon` (DMS) | `40°26′46″N`, `N40 26.767`, `40d26m46s` | degrees, minutes, seconds, any symbols or letters |
117
+ | `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 |
118
+ | `wkt` | `POINT (29 41)`, `SRID=3857;POLYGON ((…))` | all simple-feature types, Z/M/ZM, EWKT |
119
+ | `geojson` | `{"type": "Point", "coordinates": [29, 41]}` | geometry or Feature objects |
120
+ | `geohash` | `sxk97cn4` | |
121
+ | `pluscode` | `8GFJWVM5+9V` | Open Location Code; short codes with `plus_code_reference` |
122
+ | `maidenhead` | `KM69kw` | 2-10 characters |
123
+ | `mgrs` | `35T PF 66370 41552` | WGS 84, pure-Python Krüger series |
124
+ | `utm` | `35T 666371 4541552` | zone + band or hemisphere |
125
+ | `xy` | `easting` / `northing` columns | any EPSG code via the `pyproj` extra |
126
+
127
+ Every detection has a confidence and human-readable reasons:
128
+
129
+ ```text
130
+ $ geocsv detect stations.csv
131
+ latlon: lat='lon', lon='lat', decimal (confidence 0.99)
132
+ - header 'lat' looks like latitude but its values only fit longitude; the columns are swapped
133
+ - 100% of values are coordinates; 100% are within latitude/longitude range
134
+ - the two columns are adjacent
135
+ ```
136
+
137
+ Override any decision: `lat=`/`lon=`, `wkt=`, `column=` + `kind=`, `x=`/`y=` + `epsg=`,
138
+ `order="lonlat"`, `encoding=`, `delimiter=`, `header=` (CLI: `--lat`, `--lon`, `--wkt`,
139
+ `--column`, `--kind`, `--x`, `--y`, `--epsg`, `--order`, `--encoding`, `--delimiter`,
140
+ `--header/--no-header`). Columns can be named or numbered from 1.
141
+
142
+ ## 🩺 Validation report
143
+
144
+ ```text
145
+ $ geocsv inspect issues.csv
146
+ Geometry latlon: lat='latitude', lon='longitude', decimal (confidence 0.87)
147
+ Valid 8 of 13 rows bbox=(0.000000, 0.000000, 37.874600, 41.008200)
148
+
149
+ issue severity rows lines
150
+ -------------- -------- ----- --------------------
151
+ missing error 2 3, 4
152
+ invalid error 1 5
153
+ out_of_range error 1 6
154
+ swapped error 1 9
155
+ likely_swapped warning 1 13
156
+ null_island warning 1 7
157
+ duplicate warning 1 8
158
+ ragged warning 1 14
159
+ ```
160
+
161
+ | code | severity | meaning |
162
+ |---|---|---|
163
+ | `missing` | error | empty geometry cell(s) |
164
+ | `invalid` | error | the value does not parse |
165
+ | `empty` | error | WKT/GeoJSON `EMPTY` geometry |
166
+ | `out_of_range` | error | outside ±90 / ±180 |
167
+ | `swapped` | error | latitude beyond ±90 but would fit if swapped (`fix_swapped=True` repairs it) |
168
+ | `crs` | error | EWKT in another CRS and `pyproj` is not installed |
169
+ | `likely_swapped` | warning | in range, but far closer to the rest of the table when swapped |
170
+ | `swapped_fixed` | warning | swapped back (by `fix_swapped=True` or hemisphere letters) |
171
+ | `null_island` | warning | exactly (0, 0) |
172
+ | `duplicate` | warning | same geometry as an earlier row |
173
+ | `ragged` | warning | row length differs from the header |
174
+
175
+ Line numbers are physical lines in the file, as your editor shows them.
176
+ `table.issues`, `table.summary()` and `geocsv inspect --json` give the same data in
177
+ machine-readable form; `geocsv convert --strict` exits with status 1 when any row has an
178
+ error.
179
+
180
+ ## 🗺️ What it writes
181
+
182
+ | format | method | notes |
183
+ |---|---|---|
184
+ | GeoJSON | `to_geojson(path=None)` | RFC 7946 FeatureCollection; returns a dict without a path |
185
+ | GeoJSONSeq | `to_geojsonseq(path)` | one Feature per line (`.geojsonl`, `.ndjson`) |
186
+ | GeoPackage | `to_gpkg(path, layer=None)` | pure `sqlite3`; OGC GeoPackage 1.4 tables, StandardGeoPackageBinary + ISO WKB, any geometry type, Z |
187
+ | KML | `to_kml(path=None)` | Placemarks with ExtendedData, holes, MultiGeometry |
188
+ | GPX | `to_gpx(path=None)` | points as waypoints, lines as tracks |
189
+ | Shapefile | `to_shapefile(path)` | needs `geocsv[shapefile]`; one geometry family |
190
+ | any | `to_file(path, driver=None)` | picks the format from the extension |
191
+
192
+ Attributes keep their types: integer, real or text (codes like `"00123"` stay text).
193
+ A `name`-like column (`name`, `title`, `isim`, `nombre`, …) labels KML placemarks and GPX
194
+ waypoints. The GeoPackage writer is tested against `sqlite3`, GDAL's `ogrinfo`, GDAL's
195
+ `validate_gpkg.py` conformance checker and GeoPandas.
196
+
197
+ ## 🧭 Coordinate helpers
198
+
199
+ The decoders are public and dependency-free:
200
+
201
+ ```python
202
+ from geocsv import dms, geohash, pluscode, maidenhead, utm
203
+
204
+ dms.parse_dms_pair("40°26′46″N 79°58′56″W") # (40.4461, -79.9822)
205
+ dms.format_dms(40.446111, "lat") # '40°26′46.0″N'
206
+ geohash.decode("u4pruydqqvj") # (57.64911, 10.40744)
207
+ pluscode.decode("849VCWC8+R9") # (37.4220625, -122.0840625)
208
+ pluscode.recover_nearest("CWC8+R9", 37.4, -122.0)
209
+ maidenhead.decode("FN31pr") # (41.7292, -72.7083)
210
+ utm.mgrs_to_latlon("31U DQ 48251 11932") # (48.8582, 2.2945)
211
+ utm.latlon_to_mgrs(48.8582, 2.2945) # '31U DQ 48251 11932'
212
+ utm.utm_to_latlon(31, "N", 448251, 5411932)
213
+ ```
214
+
215
+ UTM/MGRS use Krüger's series to sixth order; they agree with PROJ to under a millimetre.
216
+
217
+ ## 🛠️ Development
218
+
219
+ ```bash
220
+ git clone https://github.com/YusufEminoglu/geocsv_sdk.git
221
+ cd geocsv_sdk
222
+ pip install -e ".[dev,pyproj,shapefile]"
223
+ python -m pytest --cov=geocsv
224
+ python -m ruff check . && python -m ruff format --check src tests && python -m mypy src
225
+ ```
226
+
227
+ The tests include hand-made fixture CSVs, reference values for every decoder, a seeded
228
+ synthetic corpus of 400 tables that measures detection accuracy, and GDAL-based checks
229
+ of the GeoPackage output that run when GDAL is installed.
230
+
231
+ ## 📄 License
232
+
233
+ MIT © Yusuf Eminoğlu
geocsv-0.1.0/README.md ADDED
@@ -0,0 +1,192 @@
1
+ <div align="center">
2
+
3
+ # geocsv
4
+
5
+ [![PyPI version](https://img.shields.io/pypi/v/geocsv.svg?color=10b981)](https://pypi.org/project/geocsv/)
6
+ [![Python version support](https://img.shields.io/pypi/pyversions/geocsv.svg?color=3b82f6)](https://pypi.org/project/geocsv/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/YusufEminoglu/geocsv_sdk/blob/main/LICENSE)
8
+ [![Code style: Ruff](https://img.shields.io/badge/code%20style-ruff-D7FF64.svg)](https://docs.astral.sh/ruff/)
9
+
10
+ **Turn any spreadsheet or CSV of places into a map-ready GeoJSON, GeoPackage, KML or GPX file, without GDAL.**
11
+
12
+ [📦 PyPI](https://pypi.org/project/geocsv/) • [💻 GitHub](https://github.com/YusufEminoglu/geocsv_sdk) • [🐛 Issues](https://github.com/YusufEminoglu/geocsv_sdk/issues)
13
+
14
+ </div>
15
+
16
+ You have a spreadsheet export with a column called `Breitengrad`, or `enlem;boylam`
17
+ with decimal commas, or `40°26′46″N 79°58′56″W` strings, or Plus Codes, or MGRS
18
+ references. geocsv finds the coordinates, tells you how sure it is and why, reports the
19
+ rows that are broken, and writes a file that QGIS, ArcGIS, Google Earth, a GPS or a web
20
+ map can open. It is pure Python with **no required dependencies**.
21
+
22
+ ## 📦 Installation
23
+
24
+ ```bash
25
+ pip install geocsv
26
+
27
+ # optional extras
28
+ pip install "geocsv[pyproj]" # reproject x/y or EWKT from any EPSG code
29
+ pip install "geocsv[shapefile]" # write ESRI Shapefiles (via pyshp)
30
+ ```
31
+
32
+ Python 3.9+ on Windows, macOS or Linux.
33
+
34
+ ## ⚡ Quickstart
35
+
36
+ ```bash
37
+ geocsv inspect places.csv # what was detected, and which rows are wrong
38
+ geocsv convert places.csv -o places.gpkg # GeoPackage (also .geojson .geojsonl .kml .gpx .shp)
39
+ geocsv convert places.csv -o places.geojson --lat "Y koord" --lon "X koord"
40
+ ```
41
+
42
+ ```python
43
+ import geocsv
44
+
45
+ table = geocsv.read("places.csv")
46
+ print(table.detection.describe())
47
+ # latlon: lat='Breitengrad', lon='Längengrad', decimal (confidence 0.99)
48
+ print(table.report())
49
+
50
+ table.to_gpkg("places.gpkg")
51
+ table.to_geojson("places.geojson")
52
+ fc = table.to_geojson() # or get the FeatureCollection as a dict
53
+ table.to_kml("places.kml")
54
+ table.to_gpx("places.gpx")
55
+
56
+ geocsv.detect("places.csv") # detection only, with .reasons and .alternatives
57
+ ```
58
+
59
+ ## 🔎 What it reads
60
+
61
+ **CSV dialects** (standard library only)
62
+
63
+ * encodings: UTF-8, UTF-8 with BOM, UTF-16 (Excel "Unicode text"), and the Windows code
64
+ pages cp1252 / latin-1 / cp1254 (Turkish letters such as ğ ı ş are told apart from
65
+ Western European text)
66
+ * delimiters `,` `;` tab `|` (sniffed from the data, quoted fields respected) and Excel's
67
+ `sep=;` first line
68
+ * decimal commas (`40,7128`), header or no header (detected), ragged rows
69
+
70
+ **Geometry encodings**, found automatically:
71
+
72
+ | kind | example | notes |
73
+ |---|---|---|
74
+ | `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 |
75
+ | `latlon` (DMS) | `40°26′46″N`, `N40 26.767`, `40d26m46s` | degrees, minutes, seconds, any symbols or letters |
76
+ | `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 |
77
+ | `wkt` | `POINT (29 41)`, `SRID=3857;POLYGON ((…))` | all simple-feature types, Z/M/ZM, EWKT |
78
+ | `geojson` | `{"type": "Point", "coordinates": [29, 41]}` | geometry or Feature objects |
79
+ | `geohash` | `sxk97cn4` | |
80
+ | `pluscode` | `8GFJWVM5+9V` | Open Location Code; short codes with `plus_code_reference` |
81
+ | `maidenhead` | `KM69kw` | 2-10 characters |
82
+ | `mgrs` | `35T PF 66370 41552` | WGS 84, pure-Python Krüger series |
83
+ | `utm` | `35T 666371 4541552` | zone + band or hemisphere |
84
+ | `xy` | `easting` / `northing` columns | any EPSG code via the `pyproj` extra |
85
+
86
+ Every detection has a confidence and human-readable reasons:
87
+
88
+ ```text
89
+ $ geocsv detect stations.csv
90
+ latlon: lat='lon', lon='lat', decimal (confidence 0.99)
91
+ - header 'lat' looks like latitude but its values only fit longitude; the columns are swapped
92
+ - 100% of values are coordinates; 100% are within latitude/longitude range
93
+ - the two columns are adjacent
94
+ ```
95
+
96
+ Override any decision: `lat=`/`lon=`, `wkt=`, `column=` + `kind=`, `x=`/`y=` + `epsg=`,
97
+ `order="lonlat"`, `encoding=`, `delimiter=`, `header=` (CLI: `--lat`, `--lon`, `--wkt`,
98
+ `--column`, `--kind`, `--x`, `--y`, `--epsg`, `--order`, `--encoding`, `--delimiter`,
99
+ `--header/--no-header`). Columns can be named or numbered from 1.
100
+
101
+ ## 🩺 Validation report
102
+
103
+ ```text
104
+ $ geocsv inspect issues.csv
105
+ Geometry latlon: lat='latitude', lon='longitude', decimal (confidence 0.87)
106
+ Valid 8 of 13 rows bbox=(0.000000, 0.000000, 37.874600, 41.008200)
107
+
108
+ issue severity rows lines
109
+ -------------- -------- ----- --------------------
110
+ missing error 2 3, 4
111
+ invalid error 1 5
112
+ out_of_range error 1 6
113
+ swapped error 1 9
114
+ likely_swapped warning 1 13
115
+ null_island warning 1 7
116
+ duplicate warning 1 8
117
+ ragged warning 1 14
118
+ ```
119
+
120
+ | code | severity | meaning |
121
+ |---|---|---|
122
+ | `missing` | error | empty geometry cell(s) |
123
+ | `invalid` | error | the value does not parse |
124
+ | `empty` | error | WKT/GeoJSON `EMPTY` geometry |
125
+ | `out_of_range` | error | outside ±90 / ±180 |
126
+ | `swapped` | error | latitude beyond ±90 but would fit if swapped (`fix_swapped=True` repairs it) |
127
+ | `crs` | error | EWKT in another CRS and `pyproj` is not installed |
128
+ | `likely_swapped` | warning | in range, but far closer to the rest of the table when swapped |
129
+ | `swapped_fixed` | warning | swapped back (by `fix_swapped=True` or hemisphere letters) |
130
+ | `null_island` | warning | exactly (0, 0) |
131
+ | `duplicate` | warning | same geometry as an earlier row |
132
+ | `ragged` | warning | row length differs from the header |
133
+
134
+ Line numbers are physical lines in the file, as your editor shows them.
135
+ `table.issues`, `table.summary()` and `geocsv inspect --json` give the same data in
136
+ machine-readable form; `geocsv convert --strict` exits with status 1 when any row has an
137
+ error.
138
+
139
+ ## 🗺️ What it writes
140
+
141
+ | format | method | notes |
142
+ |---|---|---|
143
+ | GeoJSON | `to_geojson(path=None)` | RFC 7946 FeatureCollection; returns a dict without a path |
144
+ | GeoJSONSeq | `to_geojsonseq(path)` | one Feature per line (`.geojsonl`, `.ndjson`) |
145
+ | GeoPackage | `to_gpkg(path, layer=None)` | pure `sqlite3`; OGC GeoPackage 1.4 tables, StandardGeoPackageBinary + ISO WKB, any geometry type, Z |
146
+ | KML | `to_kml(path=None)` | Placemarks with ExtendedData, holes, MultiGeometry |
147
+ | GPX | `to_gpx(path=None)` | points as waypoints, lines as tracks |
148
+ | Shapefile | `to_shapefile(path)` | needs `geocsv[shapefile]`; one geometry family |
149
+ | any | `to_file(path, driver=None)` | picks the format from the extension |
150
+
151
+ Attributes keep their types: integer, real or text (codes like `"00123"` stay text).
152
+ A `name`-like column (`name`, `title`, `isim`, `nombre`, …) labels KML placemarks and GPX
153
+ waypoints. The GeoPackage writer is tested against `sqlite3`, GDAL's `ogrinfo`, GDAL's
154
+ `validate_gpkg.py` conformance checker and GeoPandas.
155
+
156
+ ## 🧭 Coordinate helpers
157
+
158
+ The decoders are public and dependency-free:
159
+
160
+ ```python
161
+ from geocsv import dms, geohash, pluscode, maidenhead, utm
162
+
163
+ dms.parse_dms_pair("40°26′46″N 79°58′56″W") # (40.4461, -79.9822)
164
+ dms.format_dms(40.446111, "lat") # '40°26′46.0″N'
165
+ geohash.decode("u4pruydqqvj") # (57.64911, 10.40744)
166
+ pluscode.decode("849VCWC8+R9") # (37.4220625, -122.0840625)
167
+ pluscode.recover_nearest("CWC8+R9", 37.4, -122.0)
168
+ maidenhead.decode("FN31pr") # (41.7292, -72.7083)
169
+ utm.mgrs_to_latlon("31U DQ 48251 11932") # (48.8582, 2.2945)
170
+ utm.latlon_to_mgrs(48.8582, 2.2945) # '31U DQ 48251 11932'
171
+ utm.utm_to_latlon(31, "N", 448251, 5411932)
172
+ ```
173
+
174
+ UTM/MGRS use Krüger's series to sixth order; they agree with PROJ to under a millimetre.
175
+
176
+ ## 🛠️ Development
177
+
178
+ ```bash
179
+ git clone https://github.com/YusufEminoglu/geocsv_sdk.git
180
+ cd geocsv_sdk
181
+ pip install -e ".[dev,pyproj,shapefile]"
182
+ python -m pytest --cov=geocsv
183
+ python -m ruff check . && python -m ruff format --check src tests && python -m mypy src
184
+ ```
185
+
186
+ The tests include hand-made fixture CSVs, reference values for every decoder, a seeded
187
+ synthetic corpus of 400 tables that measures detection accuracy, and GDAL-based checks
188
+ of the GeoPackage output that run when GDAL is installed.
189
+
190
+ ## 📄 License
191
+
192
+ MIT © Yusuf Eminoğlu
@@ -0,0 +1,35 @@
1
+ """Read a few CSV layouts, print the detection report and write a GeoPackage.
2
+
3
+ Run from a clone: python examples/quickstart.py
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import tempfile
9
+ from pathlib import Path
10
+
11
+ import geocsv
12
+
13
+ SAMPLES = {
14
+ "turkish_decimal_comma.csv": "isim;enlem;boylam\nAnkara;39,9334;32,8597\nİzmir;38,4237;27,1428\n",
15
+ "dms.csv": 'site,position\nPittsburgh,"40°26′46″N 79°58′56″W"\nOslo,"59°54′50″N 10°45′08″E"\n',
16
+ "wkt.csv": 'name,wkt\nPark,"POLYGON ((29 41, 29.01 41, 29.01 41.01, 29 41))"\n',
17
+ "codes.csv": "shop,plus_code\nGoogleplex,849VCWC8+R9\nAnkara,8GFJWVM5+9V\n",
18
+ }
19
+
20
+
21
+ def main() -> None:
22
+ with tempfile.TemporaryDirectory() as tmp:
23
+ folder = Path(tmp)
24
+ for name, text in SAMPLES.items():
25
+ path = folder / name
26
+ path.write_text(text, encoding="utf-8")
27
+ table = geocsv.read(path)
28
+ print(table.report())
29
+ print()
30
+ out = table.to_gpkg(path.with_suffix(".gpkg"))
31
+ print(f"wrote {out.name} with {len(table.valid)} features\n")
32
+
33
+
34
+ if __name__ == "__main__":
35
+ main()
@@ -0,0 +1,133 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "geocsv"
7
+ version = "0.1.0"
8
+ authors = [
9
+ { name = "Yusuf Eminoğlu", email = "yusufeminoglu@gmail.com" }
10
+ ]
11
+ description = "Turn any spreadsheet or CSV of places into a map-ready GeoJSON, GeoPackage, KML or GPX file, without GDAL."
12
+ keywords = [
13
+ "gis",
14
+ "csv",
15
+ "geojson",
16
+ "geopackage",
17
+ "gpkg",
18
+ "kml",
19
+ "gpx",
20
+ "wkt",
21
+ "latitude",
22
+ "longitude",
23
+ "geocoding",
24
+ "dms",
25
+ "geohash",
26
+ "plus-codes",
27
+ "open-location-code",
28
+ "mgrs",
29
+ "utm",
30
+ "maidenhead",
31
+ "spreadsheet",
32
+ "data-cleaning",
33
+ ]
34
+ readme = "README.md"
35
+ license = "MIT"
36
+ license-files = ["LICENSE"]
37
+ requires-python = ">=3.9"
38
+ classifiers = [
39
+ "Development Status :: 4 - Beta",
40
+ "Programming Language :: Python :: 3",
41
+ "Programming Language :: Python :: 3.9",
42
+ "Programming Language :: Python :: 3.10",
43
+ "Programming Language :: Python :: 3.11",
44
+ "Programming Language :: Python :: 3.12",
45
+ "Programming Language :: Python :: 3.13",
46
+ "Operating System :: OS Independent",
47
+ "Topic :: Scientific/Engineering :: GIS",
48
+ "Topic :: Text Processing",
49
+ "Topic :: Utilities",
50
+ "Environment :: Console",
51
+ "Intended Audience :: Science/Research",
52
+ "Intended Audience :: Developers",
53
+ "Intended Audience :: End Users/Desktop",
54
+ "Typing :: Typed",
55
+ ]
56
+ dependencies = []
57
+
58
+ [project.optional-dependencies]
59
+ pyproj = [
60
+ "pyproj>=3.3.0",
61
+ ]
62
+ shapefile = [
63
+ "pyshp>=2.3.0",
64
+ ]
65
+ dev = [
66
+ "pytest>=7.0.0",
67
+ "pytest-cov>=4.0.0",
68
+ "ruff>=0.1.0",
69
+ "mypy>=1.0.0",
70
+ ]
71
+
72
+ [project.scripts]
73
+ geocsv = "geocsv.cli:main"
74
+
75
+ [project.urls]
76
+ Homepage = "https://github.com/YusufEminoglu/geocsv_sdk"
77
+ Repository = "https://github.com/YusufEminoglu/geocsv_sdk"
78
+ Issues = "https://github.com/YusufEminoglu/geocsv_sdk/issues"
79
+ Changelog = "https://github.com/YusufEminoglu/geocsv_sdk/blob/main/CHANGELOG.md"
80
+
81
+ [tool.setuptools]
82
+ package-dir = {"" = "src"}
83
+
84
+ [tool.setuptools.packages.find]
85
+ where = ["src"]
86
+
87
+ [tool.setuptools.package-data]
88
+ geocsv = ["py.typed"]
89
+
90
+ [tool.ruff]
91
+ line-length = 100
92
+ target-version = "py39"
93
+ exclude = [
94
+ "scratch",
95
+ ".venv",
96
+ "build",
97
+ "dist",
98
+ ]
99
+
100
+ [tool.ruff.lint]
101
+ select = ["E", "F", "W", "I", "B", "C4"]
102
+ ignore = ["E501"]
103
+
104
+ [tool.mypy]
105
+ files = ["src"]
106
+ warn_unused_ignores = true
107
+ warn_redundant_casts = true
108
+
109
+ [[tool.mypy.overrides]]
110
+ module = [
111
+ # Optional extras: every import of them is lazy and guarded.
112
+ "pyproj.*",
113
+ "shapefile.*",
114
+ ]
115
+ ignore_missing_imports = true
116
+
117
+ [tool.pytest.ini_options]
118
+ testpaths = ["tests"]
119
+ # Lets `pytest` run straight from a clone, without an editable install first.
120
+ pythonpath = ["src"]
121
+
122
+ [tool.coverage.run]
123
+ source = ["geocsv"]
124
+ branch = true
125
+
126
+ [tool.coverage.report]
127
+ show_missing = true
128
+ fail_under = 85
129
+ exclude_lines = [
130
+ "pragma: no cover",
131
+ "if TYPE_CHECKING:",
132
+ "raise NotImplementedError",
133
+ ]
geocsv-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+