CSV to WKT Converter

A CSV to WKT converter turns coordinate columns into Well-Known Text, the geometry literal PostGIS, GeoPandas, DuckDB, SpatiaLite and QGIS all read as plain text. This one writes POINT, LINESTRING, POLYGON and MULTIPOINT, closes polygon rings for you, and can keep every attribute alongside the geometry so the table loads in one step. Nothing is uploaded.

Need to clean or group the rows first? Open the app

Spaces and commas mean different things

WKT looks simple and has one piece of syntax people get backwards. Inside a position, the two numbers are separated by a space. Between positions, the separator is a comma.

POINT (2.3522 48.8566)
LINESTRING (2.35 48.86, 2.36 48.87)
POLYGON ((2.35 48.86, 2.36 48.87, 2.37 48.85, 2.35 48.86))

That is the opposite of KML, where the comma separates the numbers within a tuple and whitespace separates tuples. If you have been working with KML that morning, writing POINT (2.3522,48.8566) is an easy reflex, and every parser rejects it.

The other half is axis order: longitude first, then latitude, same as GeoJSON and KML and the reverse of how a spreadsheet lists them. That is applied here from your column names, so a file with latitude and longitude headings comes out correct without you reversing anything.

Note the double parentheses on POLYGON. The outer pair holds the polygon, the inner pair holds one ring. A polygon can have several rings, the first being the outer boundary and the rest being holes, which is why the nesting exists even when there is only one.

A worked example

Three cities with attributes:

name,latitude,longitude,population,code
London,51.5072,-0.1276,9648000,0047
Sydney,-33.8688,151.2093,5185000,0250
Cairo,30.0444,31.2357,21750000,0969

With POINT geometry and the column layout, you get the original table plus one column:

name,latitude,longitude,population,code,wkt
London,51.5072,-0.1276,9648000,0047,POINT (-0.1276 51.5072)
Sydney,-33.8688,151.2093,5185000,0250,POINT (151.2093 -33.8688)
Cairo,30.0444,31.2357,21750000,0969,POINT (31.2357 30.0444)

That loads into PostGIS in two statements. Copy it into a staging table with wkt as text, then:

ALTER TABLE places ADD COLUMN geom geometry(Point, 4326);
UPDATE places SET geom = ST_GeomFromText(wkt, 4326);
CREATE INDEX places_geom_idx ON places USING GIST (geom);

Or in Python, one line: gdf = gpd.GeoDataFrame(df, geometry=gpd.GeoSeries.from_wkt(df.wkt), crs="EPSG:4326").

Switch to LINESTRING and pick a grouping column, and the same rows collapse into one literal per group. That output uses the list layout by default, because a line built from forty rows has no single set of attributes to carry.

Rings, SRIDs and quoting

A polygon ring must close. The first position has to appear again as the last, and if it does not, ST_GeomFromText raises an error about an unclosed ring rather than doing anything helpful. The ring is closed here automatically, and skipped when the last vertex already matches the first, so the output never carries a duplicated point you did not ask for.

Plain WKT carries no coordinate reference system. The literal is just numbers, and the meaning comes from whatever SRID you supply on load. That is why the PostGIS call takes 4326 as a second argument. If you would rather the geometry carry its own, turn on the EWKT option and each literal is prefixed SRID=4326;, which ST_GeomFromEWKT and PostGIS's implicit casts both read. EWKT is a PostGIS extension rather than part of the OGC standard, so check your target accepts it before using it.

In the column layout, the CSV writer quotes any literal that contains a comma, which means a POINT stays unquoted and a LINESTRING gets quotes. That is ordinary CSV quoting and every reader handles it; it is worth knowing only because a quoted geometry in a text editor can look like it went wrong.

Rows without a usable coordinate are skipped, counted and listed by line number with the value that failed, the same as on the other geo converters. A group with fewer points than its geometry needs is reported separately, with its count, since that usually means the grouping column is not the one you meant.

Questions

What is the difference between the two output layouts?

The column layout gives you the whole original table with a wkt column added, which is what you load into PostGIS so every attribute arrives with its geometry. The list layout gives you nothing but the literals, one per line, which is what you paste into a query or a QGIS scratch layer. Point geometry defaults to the column layout; grouped geometry defaults to the list, since a line collapses many rows into one and most of the attributes no longer apply.

How do I load the result into PostGIS?

Copy the CSV into a staging table with the wkt column as text, then add a geometry column with ST_GeomFromText(wkt, 4326). The 4326 is the SRID for WGS 84 decimal degrees, which is what this writes. If you prefer the geometry to carry its own SRID, turn on the EWKT option and the literals come out prefixed with SRID=4326; which ST_GeomFromEWKT reads directly.

Which order are the numbers in?

Longitude then latitude, separated by a space, with commas between points. So POINT (-0.1276 51.5072) and LINESTRING (0 0, 1 1). It is the same axis order as GeoJSON and KML and the reverse of how a spreadsheet usually lists them, and it is applied here from your column names so you do not have to think about it. The space-versus-comma distinction is the other half: spaces separate the two numbers of one position, commas separate positions.

Does it close polygon rings?

Yes, automatically. A WKT polygon ring has to end where it started, with the first position repeated as the last, and an unclosed ring is the single most common reason ST_GeomFromText throws. If the last vertex already matches the first, nothing is added. Asking somebody to duplicate the first row of every zone by hand is not a conversion.

What happens to a group with too few points?

It is left out and named in the warnings with its point count. A LINESTRING needs two positions and a POLYGON needs three before closing, and a group with fewer cannot become either. Writing a degenerate geometry that a database will reject later is worse than reporting it now, so the groups that fell short are listed for you to look at.

Can GeoPandas read this?

Yes. Read the CSV with pandas, then geopandas.GeoSeries.from_wkt on the wkt column, or shapely.wkt.loads a literal at a time. Setting crs to EPSG:4326 on the resulting GeoDataFrame matches what this writes. The same text works with PostGIS, SpatiaLite, DuckDB's spatial extension, QGIS and anything else built on GEOS.

Is anything uploaded?

No. The conversion runs in your browser tab with no server round trip, nothing stored between visits and no row cap. Coordinates are frequently the most sensitive column in a dataset, so a converter that never receives the file is the right shape for this job.

Convert your CSV to WKT

No sign-up, no upload, no row cap. Four geometry types, rings closed for you, attributes kept alongside.

Back to the converter