Shapely Basics: Working with Geometry Objects in Python

Problem statement

A common GIS task is creating geometry directly in Python before doing any larger workflow. You may need to build a point from GPS coordinates, define a route as a line, create a parcel boundary as a polygon, or test whether a site falls inside a study area.

This is where Shapely becomes useful. Shapely gives you geometry objects you can create, inspect, compare, and transform in memory.

This page covers:

  • creating common Shapely geometry objects
  • reading basic geometry properties
  • checking spatial relationships
  • running simple geometry operations
  • converting geometry to readable text

This page does not cover:

  • reading shapefiles or GeoJSON with GeoPandas
  • CRS management in detail
  • advanced topology rules or performance tuning

Quick answer

Shapely lets you work with geometry objects such as Point, LineString, and Polygon in pure Python.

Basic workflow:

  1. import Shapely geometry classes
  2. create objects from coordinates
  3. inspect properties like area, length, bounds, and centroid
  4. test relationships like intersects() and contains()
  5. apply simple operations like buffer()
from shapely.geometry import Point, LineString, Polygon

point = Point(12.5, 41.9)
line = LineString([(0, 0), (2, 2), (4, 1)])
polygon = Polygon([(0, 0), (4, 0), (4, 3), (0, 3), (0, 0)])

print(point.geom_type)
print(line.length)
print(polygon.area)
print(polygon.contains(point))

Step-by-step solution

Import Shapely geometry classes

For basic geometry work, import the main geometry types from shapely.geometry.

from shapely.geometry import (
    Point,
    LineString,
    Polygon,
    MultiPoint,
    MultiLineString,
    MultiPolygon,
)

The most common classes are:

  • Point for a single location
  • LineString for a connected path
  • Polygon for an area
  • multipart types such as MultiPoint and MultiPolygon when one feature has multiple parts

Create a Point geometry

A Point is built from x and y coordinates.

from shapely.geometry import Point

site = Point(500000, 4649776)
print(site)
print(site.geom_type)

Typical GIS uses:

  • site locations
  • GPS observations
  • address coordinates
  • sampling points

This is the standard pattern when you need to create geometry from raw coordinates in Python.

Create a LineString geometry

A LineString is an ordered sequence of coordinate pairs.

from shapely.geometry import LineString

road_segment = LineString([
    (500000, 4649700),
    (500100, 4649750),
    (500250, 4649800)
])

print(road_segment)
print(road_segment.geom_type)

Typical uses:

  • roads
  • rivers
  • walking paths
  • route segments

Order matters because the line follows the coordinates in the sequence you provide.

Create a Polygon geometry

A Polygon represents an area. It is created from a ring of coordinates.

from shapely.geometry import Polygon

study_area = Polygon([
    (500000, 4649700),
    (500300, 4649700),
    (500300, 4650000),
    (500000, 4650000),
    (500000, 4649700)
])

print(study_area)
print(study_area.geom_type)

Typical uses:

  • land parcels
  • administrative boundaries
  • service zones
  • site extents

Shapely can close the ring for you, but repeating the first coordinate at the end makes the geometry definition clearer.

Inspect geometry properties

Once you create a geometry, you can inspect its key properties.

print(study_area.geom_type)     # Polygon
print(study_area.bounds)        # (minx, miny, maxx, maxy)
print(study_area.centroid)      # center point
print(study_area.area)          # area for polygons
print(road_segment.length)      # line length
print(site.x, site.y)           # point coordinates

Useful properties in GIS work:

  • geom_type: confirms the geometry type
  • bounds: helpful for extent checks and filtering
  • centroid: useful for labels or quick reference points
  • area: used for polygon measurement
  • length: used for line length and polygon perimeter
  • point x and y: direct coordinate access

Check spatial relationships between geometries

Shapely supports common spatial relationship checks.

print(study_area.contains(site))
print(site.within(study_area))
print(road_segment.intersects(study_area))
print(road_segment.touches(study_area))

These methods are useful for:

  • point-in-polygon tests
  • overlap checks
  • boundary contact checks
  • filtering features by location

In a real workflow, this is often the step where you decide which features should be kept, clipped, flagged, or passed into a GeoPandas analysis.

Run simple geometry operations

Shapely geometry operations return new geometry objects.

buffered_site = site.buffer(100)
print(buffered_site.geom_type)
print(buffered_site.area)

intersection_geom = road_segment.intersection(study_area)
print(intersection_geom.geom_type)

distance_to_boundary = site.distance(study_area.boundary)
print(distance_to_boundary)

combined = study_area.union(buffered_site)
print(combined.geom_type)

Common operations:

  • buffer(distance) creates an area around a geometry
  • intersection(other) returns the shared part
  • union(other) merges geometries
  • distance(other) calculates distance between geometries

These operations are often used to build study buffers, trim features to an analysis area, combine overlapping zones, or measure proximity.

Convert geometry to readable formats

Readable output helps with debugging and exporting.

print(site.wkt)
print(study_area.wkt)
print(site.__geo_interface__)

Useful formats:

  • wkt: Well-Known Text such as POINT (500000 4649776)
  • __geo_interface__: a GeoJSON-like mapping structure

WKT is useful for logs, debugging, database inserts, and quick inspection.

Code examples

Example 1: Create basic geometry objects

from shapely.geometry import Point, LineString, Polygon

point = Point(10, 20)
line = LineString([(0, 0), (2, 1), (4, 3)])
polygon = Polygon([(0, 0), (4, 0), (4, 4), (0, 4), (0, 0)])

print(point)
print(line)
print(polygon)

Example 2: Read common geometry properties

from shapely.geometry import Point, LineString, Polygon

point = Point(10, 20)
line = LineString([(0, 0), (3, 4)])
polygon = Polygon([(0, 0), (6, 0), (6, 4), (0, 4), (0, 0)])

print(point.geom_type)
print(point.bounds)
print(point.centroid)

print(line.geom_type)
print(line.length)
print(line.bounds)

print(polygon.geom_type)
print(polygon.area)
print(polygon.length)
print(polygon.centroid)

Example 3: Test spatial relationships

from shapely.geometry import Point, Polygon, LineString

point = Point(2, 2)
polygon = Polygon([(0, 0), (5, 0), (5, 5), (0, 5), (0, 0)])
line = LineString([(4, 4), (8, 8)])

print(polygon.contains(point))   # True
print(point.within(polygon))     # True
print(line.intersects(polygon))  # True
print(line.touches(polygon))     # False

Example 4: Apply a simple buffer

from shapely.geometry import Point

well = Point(100, 200)
buffer_zone = well.buffer(50)

print(buffer_zone.geom_type)
print(buffer_zone.area)
print(buffer_zone.bounds)

Example 5: Export geometry to WKT

from shapely.geometry import Polygon

parcel = Polygon([
    (1000, 1000),
    (1100, 1000),
    (1100, 1050),
    (1000, 1050),
    (1000, 1000)
])

print(parcel.wkt)

Explanation

Shapely works with geometry objects in memory. It does not read shapefiles by itself and it does not manage attribute tables like GeoPandas does. Its job is geometry creation and geometry logic.

It helps to separate three things:

  • coordinate lists: raw tuples such as [(0, 0), (1, 1)]
  • geometry objects: Shapely objects such as LineString(...)
  • geometry methods and properties: tools like .area, .buffer(), and .intersects()

In practice, many Python GIS workflows start here. You create or receive geometry objects, test them, measure them, or transform them, then later place them into a GeoDataFrame for file export, filtering, clipping, joins, or larger analysis.

Edge cases or notes

Geometry objects do not store CRS by themselves

A Shapely geometry is just coordinates. It does not know whether those values are in EPSG:4326, UTM, or a local projected CRS. If you need CRS-aware workflows, use GeoPandas or manage CRS separately.

Polygon rings should be valid

Self-intersecting polygons or badly constructed rings can produce invalid geometry. That can break overlay or area calculations. You can do a basic check with geometry.is_valid.

print(study_area.is_valid)

If you run into problems, see How to Fix Invalid Geometry Errors in Shapely.

Area and length depend on coordinate units

If your coordinates are in longitude and latitude, area and length are not in meters. For meaningful measurements, use an appropriate projected CRS before calculating. For background, see Coordinate Reference Systems (CRS) Explained for Python GIS.

Operations return new geometries

Methods such as buffer() and intersection() do not change the original object in place.

new_geom = site.buffer(100)

Some results may be multipart geometries

An intersection or union can return MultiPolygon, MultiLineString, or an empty geometry depending on the inputs.

For broader context, read Python for GIS: What It Is and When to Use It.

Related task pages:

Related background pages:

Troubleshooting:

FAQ

What is Shapely used for in Python GIS?

Shapely is used to create and manipulate geometry objects in Python. Common tasks include building points, lines, and polygons, checking intersections, measuring area or distance, and running operations such as buffer or union.

What geometry types can I create with Shapely?

You can create Point, LineString, Polygon, MultiPoint, MultiLineString, and MultiPolygon, along with other geometry types such as geometry collections.

Does Shapely handle coordinate reference systems?

No. Shapely geometry objects do not store CRS information by themselves. If you need CRS management, use GeoPandas or another GIS structure that tracks CRS separately.

What is the difference between Shapely and GeoPandas?

Shapely handles geometry objects and geometry operations. GeoPandas builds on Shapely and adds tabular data handling, file I/O, CRS support, and dataframe-style spatial workflows.

Why is my polygon area incorrect in Shapely?

The most common reason is using geographic coordinates instead of a projected CRS. If your polygon coordinates are in degrees, the area result is not a real square-meter value. Invalid polygon geometry can also cause incorrect results.