outdooractive/gis-tools-csv
CSV read and write support for Swift, built on top of gis-tools. Parses CSV rows into typed FeatureCollection objects — and writes them back as CSV.
Features
- Reads and writes CSV files, with a configurable delimiter (
,;\tor any character) - Works directly with Postgres/Postgis CSV exports (
COPY TO) - Header required — geometry is never guessed
- Geometry columns (
geometry,geom) parsed with format auto-detection: WKT (with or without anSRID=…;prefix), hex-encoded WKB/EWKB/TWKB, or GeoJSON - Point geometry from
latitude/longitude(and aliases), with optionalaltitude - Feature ids from an
idcolumn (and many aliases), matched case-insensitively - All other columns become
Featureproperties (booleans, ints, doubles, strings) - RFC 4180-style quoting: embedded delimiters, quotes, and newlines
- Writing always emits a header; uses
geometry(WKT) for complex features andlongitude/latitude/altitudefor simple points
Requirements
Swift 6.1 or higher. Compiles on iOS (≥ iOS 15), macOS (≥ macOS 15), tvOS (≥ tvOS 15), watchOS (≥ watchOS 7), Linux, Android and Wasm. No external dependencies beyond the base gis-tools package.
Installation with Swift Package Manager
dependencies: [
.package(url: "https://github.com/Outdooractive/gis-tools-csv", from: "1.0.0"),
.package(url: "https://github.com/Outdooractive/gis-tools", from: "2.0.0"),
],
targets: [
.target(name: "MyTarget", dependencies: [
.product(name: "GISToolsCSV", package: "gis-tools-csv"),
.product(name: "GISTools", package: "gis-tools"),
]),
]Usage
Reading
import GISTools
import GISToolsCSV
let url = URL(fileURLWithPath: "/path/to/file.csv")
let fc = try CSVCoder.read(from: url)
// With a non-default delimiter:
let fc = try CSVCoder.read(from: url, options: CSVReadOptions(delimiter: ";"))
// Omit NULL and empty values from properties (e.g. PostGIS exports):
let fc = try CSVCoder.read(from: url, options: CSVReadOptions(nullHandling: .omit))
// Concatenate all coordinates into a single LineString (e.g. GPS tracks):
let fc = try CSVCoder.read(from: url, options: CSVReadOptions(treatAsLineString: true))
// Or via the convenience init:
guard let fc = FeatureCollection(csv: url) else { return }CSVReadOptions controls how a CSV is read:
| Option | Default | Description | |---|---|---| | delimiter | "," | The field delimiter. | | nullHandling | .keepAsString | How NULL and empty values are treated. .keepAsString keeps them as strings; .omit drops the property entirely. NULL is matched case-insensitively. | | treatAsLineString | false | Concatenates the coordinates of all rows (in row order) into a single LineString feature, dropping row properties and ids. Works for latitude/longitude rows and for POINT/MULTIPOINT/LINESTRING/MULTILINESTRING geometries in a geometry/geom column; other geometry types and fewer than 2 coordinates in total are errors. |
Writing
try fc.writeCSV(to: outputURL)
// Or via the coder directly:
try CSVCoder.write(fc, to: outputURL)
// Serialize to Data:
let data = try CSVCoder.write(fc)
// Write geometry as EWKB hex:
try CSVCoder.write(fc, to: outputURL, options: CSVWriteOptions(geometryFormat: .ewkb))CSVWriteOptions controls how a FeatureCollection is written:
| Option | Default | Description | |---|---|---| | delimiter | "," | The field delimiter. | | geometryFormat | .auto | How geometry is written. .auto uses longitude/latitude/altitude columns for all-points collections and a WKT geometry column otherwise. .wkt, .ewkb (hex), and .geojson always emit a geometry column. | | geometryColumnName | "geometry" | The name of the geometry column. | | includeHeader | true | Whether to write the header row. | | nullValue | "" | The value written for nil properties (e.g. "NULL"). | | lineEnding | .lf | The line ending between rows (.lf or .crlf). |
Column mapping (reading)
The header row is matched case-insensitively:
| Role | Accepted column names | |-----------------|------------------------------------------------| | Geometry | geometry, geom | | Latitude | latitude, lat | | Longitude | longitude, long, lng | | Altitude | altitude, elevation, elev, z | | Feature id | id, feature_id, feature_identifier, identifier, fid, objectid, … |
Any other column becomes a `Feature property. Numeric properties are parsed as Int or Double, true/false as Bool, everything else stays a String`.
If a row has a geometry column it is used as-is. The format is auto-detected, so it may be WKT (e.g. POINT (11.5 48.1) or SRID=4326;LINESTRING (…)), a hex-encoded PostGIS EWKB/WKB/TWKB string, or GeoJSON — and it may decode to a Point, LineString, Polygon, … Otherwise, if a latitude and longitude column are both present, a Point is built. A row with neither is an error.
Column mapping (writing)
The header is always written. If every feature is a simple Point, the output uses id, longitude, latitude, altitude, then the remaining property columns. Otherwise the geometry column is written last (it can be long) as WKT, named geometry:
// all points:
id,longitude,latitude,altitude,name
1,11.518585,48.135125,520,Marienplatz
// mixed/complex:
id,name,geometry
2,Trail,"SRID=4326;LINESTRING(10.22 47.56,10.3 47.62)"Feature ids are always written to the id column (empty if a feature has none).
API Reference
| API | Description | |---|---| | CSVCoder.read(from url:options:) | Reads a CSV file into a FeatureCollection | | CSVCoder.read(from data:options:) | Reads CSV data into a FeatureCollection | | CSVCoder.write(:options:) | Serializes a FeatureCollection to CSV Data | | CSVCoder.write(:to:options:) | Writes a FeatureCollection to a CSV file | | CSVReadOptions | Options controlling CSV reading (delimiter, nullHandling) | | CSVWriteOptions | Options controlling CSV writing (delimiter, geometryFormat, geometryColumnName, includeHeader, nullValue, lineEnding) | | FeatureCollection(csv:options:) | Convenience init from a CSV file | | FeatureCollection(csvData:options:) | Convenience init from CSV data | | FeatureCollection.csvData(options:) | Serializes the receiver to CSV Data | | FeatureCollection.writeCSV(to:options:) | Writes the receiver as a CSV file |
Limitations
- A header row is required — geometry columns are never guessed.
- Written geometry uses the configured
geometryColumnName(defaultgeometry) and is emitted as WKT by default. - Property columns on write are ordered by first appearance across all features.
Contributing
Please create an issue or open a pull request with a fix or enhancement.
License
MIT
Package Metadata
Repository: outdooractive/gis-tools-csv
Default branch: main
README: README.md