How to Convert Points to Lines in GeoPandas

Problem statement

A common GIS task is turning point features into line features. This usually happens when you have ordered point records that represent a path, route, or track.

Typical examples:

  • GPS track points that need to become travel lines
  • survey points that define a route in sequence
  • asset inspection points grouped by a route or feature ID
  • multiple point groups that should become separate line features

The main requirement is order. GeoPandas and Shapely can build a line from points, but they connect points in the order you provide them. If the points are not sorted correctly, the output line will be wrong.

For most GeoPandas points-to-lines workflows, you need to:

  1. load the point layer
  2. identify the field that defines point order
  3. group points that belong to the same line
  4. create LineString geometries from each ordered group

Quick answer

To convert points to lines in GeoPandas, read your point layer into a GeoDataFrame, sort the points by a sequence field or timestamp, group them by a route or track ID if needed, and build Shapely LineString geometries from each ordered point set.

import geopandas as gpd
from shapely.geometry import LineString

gdf = gpd.read_file("gps_points.gpkg")

gdf = gdf[gdf.geometry.notnull()].copy()
gdf = gdf[gdf.geom_type == "Point"].copy()
gdf = gdf.sort_values(["route_id", "sequence"])

lines = (
    gdf.groupby("route_id")["geometry"]
    .apply(lambda points: LineString(points.tolist()) if len(points) >= 2 else None)
    .dropna()
)

lines_gdf = gpd.GeoDataFrame(
    {"route_id": lines.index, "geometry": list(lines)},
    crs=gdf.crs
)

This creates one line per route_id, using the point order defined by sequence.

Step-by-step solution

Load the point dataset

Start by reading your point file. GeoPandas supports shapefiles, GeoJSON, and GeoPackage.

import geopandas as gpd

gdf = gpd.read_file("data/track_points.gpkg")
print(gdf.head())
print(gdf.geom_type.unique())
print(gdf.columns)

Check that:

  • the geometry type is Point
  • the layer includes fields such as route_id, trip_id, timestamp, or sequence
  • the data looks complete before building lines

If the geometry is not Point, stop and inspect the source data. The examples here assume Point features, not MultiPoint.

Check the coordinate reference system

The CRS does not change how you create a line from points, but it still matters for GIS workflows.

print(gdf.crs)

If your data is in geographic coordinates like EPSG:4326, line creation will still work. But if you later calculate distance or length, a projected CRS is usually better.

Example reprojection:

gdf = gdf.to_crs("EPSG:3857")

Use an appropriate projected CRS for your area if the output will be used for measurement.

Sort points into the correct order

This is the most important step. A line is created from the point sequence you provide. If the points are unordered, the line may zigzag or connect features incorrectly.

Common sort fields:

  • sequence
  • timestamp
  • station
  • point_order

Example:

gdf = gdf.sort_values("sequence")

If you have multiple lines in the same dataset, sort by both the group field and the order field:

gdf = gdf.sort_values(["route_id", "timestamp"])

If your timestamp field is stored as text, parse it before sorting:

gdf["timestamp"] = gpd.pd.to_datetime(gdf["timestamp"])
gdf = gdf.sort_values(["route_id", "timestamp"])

Group points that belong to the same line

If all points belong to one line, no grouping is needed. If the dataset contains multiple routes or tracks, group by the relevant ID field.

Examples:

  • one route per route_id
  • one GPS track per trip_id
  • one asset path per asset_id

Code examples

Example 1: Convert a single ordered set of points into one line

import geopandas as gpd
from shapely.geometry import LineString

gdf = gpd.read_file("data/survey_points.shp")

# Keep valid Point rows only
gdf = gdf[gdf.geometry.notnull()].copy()
gdf = gdf[gdf.geom_type == "Point"].copy()

# Sort by sequence
gdf = gdf.sort_values("sequence")

# Build one line from all points
if len(gdf) < 2:
    raise ValueError("At least two points are required to create a line.")

line = LineString(gdf.geometry.tolist())

line_gdf = gpd.GeoDataFrame(
    {"name": ["survey_route_1"]},
    geometry=[line],
    crs=gdf.crs
)

print(line_gdf)

Use this pattern when all records belong to one feature.

Example 2: Convert grouped points into multiple lines

This is the more common real-world case.

import pandas as pd
import geopandas as gpd
from shapely.geometry import LineString

gdf = gpd.read_file("data/gps_points.gpkg")

# Keep valid Point rows only
gdf = gdf[gdf.geometry.notnull()].copy()
gdf = gdf[gdf.geom_type == "Point"].copy()

# Parse timestamps if needed
gdf["timestamp"] = pd.to_datetime(gdf["timestamp"])

line_records = []

for route_id, group in gdf.groupby("route_id"):
    group = group.sort_values("timestamp")

    if len(group) < 2:
        continue

    line = LineString(group.geometry.tolist())

    line_records.append(
        {
            "route_id": route_id,
            "point_count": len(group),
            "geometry": line
        }
    )

lines_gdf = gpd.GeoDataFrame(line_records, crs=gdf.crs)

print(lines_gdf.head())
print(lines_gdf.geom_type.unique())

This creates one line per route_id and uses timestamp order within each group.

If you need to preserve additional group attributes, add them while building line_records. For example, you might keep a route name, start time, or end time.

Example 3: Export the resulting line GeoDataFrame

Once the new line GeoDataFrame is ready, export it to a GIS file.

lines_gdf.to_file("output/routes.gpkg", driver="GPKG")

You can also write a shapefile or GeoJSON:

lines_gdf.to_file("output/routes.shp")
lines_gdf.to_file("output/routes.geojson", driver="GeoJSON")

To confirm the result:

print(lines_gdf.geom_type.unique())

You should see LineString.

Explanation

GeoPandas manages spatial data in a GeoDataFrame, but line creation itself comes from Shapely. When you pass an ordered list of Point geometries into LineString, Shapely creates a line whose vertices follow that exact order.

That makes sorting essential.

If GPS points are collected over time, the correct order often comes from a timestamp field. If survey points were assigned a route order, the correct field may be sequence or station. Without sorting, the geometry may still be valid, but it may represent the wrong path.

Grouping is the second key step. If your dataset contains several routes and you do not group them first, all points may be merged into one incorrect line. In practice, most workflows need both:

  • a grouping field for line membership
  • an order field for vertex sequence

That is why GPS track workflows usually sort by timestamp within each trip or route ID before building the line.

Edge cases or notes

Points are not in the correct order

This is the most common reason a line looks wrong. The output line may loop, zigzag, or cross itself. Always sort using a reliable field before creating the line.

Missing grouping field

If no grouping field exists, all points will be treated as one feature. That is correct only if the dataset truly represents one line.

Duplicate or repeated points

Repeated points are allowed, but they create redundant vertices. If needed, remove duplicates before building lines.

gdf = gdf.drop_duplicates(subset=["route_id", "sequence"])

Too few points in a group

A line needs at least two points. If a group has only one point, skip it or flag it for review.

Timestamp field is stored as text

String timestamps may sort incorrectly if they are not parsed first. Convert them to datetime before sorting.

import pandas as pd

gdf["timestamp"] = pd.to_datetime(gdf["timestamp"])

CRS issues

CRS does not affect the line-building logic, but it affects later analysis. If you plan to calculate line length, reproject to a suitable projected CRS first.

Invalid geometries and null values

Point layers can contain null geometries or unexpected geometry types. Filter them before processing.

gdf = gdf[gdf.geometry.notnull()]
gdf = gdf[gdf.geom_type == "Point"]

MultiPoint input is not handled here

These examples assume one Point feature per row. If your source data uses MultiPoint, you need to explode or restructure it first before creating lines.

Multipart workflows

If one logical feature should become several segments, you need additional grouping logic, such as grouping by both route_id and segment_id.

For a broader overview, see Working with vector geometries in GeoPandas.

Related tasks:

If your output is connecting points incorrectly, check Why Is My GeoPandas Line Geometry Wrong?.

FAQ

How do I convert points to a line in GeoPandas?

Read the point layer with GeoPandas, sort the records by the correct order field, and pass the ordered point geometries to Shapely LineString.

How do I create multiple lines from grouped points in GeoPandas?

Group the GeoDataFrame by a field such as route_id or trip_id, sort each group by sequence or timestamp, and build one LineString per group.

Do the points need to be sorted before creating a line?

Yes. Line vertices are created in the exact order of the input points. Unsorted points usually produce incorrect lines.

Can I build lines from GPS points using timestamps?

Yes. This is a common workflow. Sort each GPS track by timestamp, then create one line per trip or track ID.

What happens if a group has only one point?

A valid line requires at least two points. Groups with one point should be skipped, stored separately, or reviewed as incomplete data.