Claude Code for Shapely: Geometric Operations in Python — Claude Skills 360 Blog
Blog / AI / Claude Code for Shapely: Geometric Operations in Python
AI

Claude Code for Shapely: Geometric Operations in Python

Published: June 2, 2028
Read time: 5 min read
By: Claude Skills 360

Shapely creates and analyzes geometric objects in Python. pip install shapely. Point: from shapely.geometry import Point; p = Point(1.0, 2.0). LineString: from shapely.geometry import LineString; line = LineString([(0,0),(1,1),(2,0)]). Polygon: from shapely.geometry import Polygon; poly = Polygon([(0,0),(1,0),(1,1),(0,1)]). With hole: Polygon(exterior, [hole_coords]). Multi: MultiPolygon([poly1, poly2]). WKT: from shapely import wkt; p = wkt.loads("POINT (1 2)"). GeoJSON: from shapely.geometry import shape; geom = shape(geojson_dict). to_wkt: poly.wkt. to_geojson: from shapely.geometry import mapping; mapping(poly). Contains: poly.contains(point). Intersects: a.intersects(b). Within: a.within(b). Crosses: a.crosses(b). Touches: a.touches(b). Disjoint: a.disjoint(b). Intersection: a.intersection(b). Union: a.union(b). Difference: a.difference(b). Sym diff: a.symmetric_difference(b). Buffer: p.buffer(1.0) — circle radius 1. Line buffer: line.buffer(0.5) — corridor. Simplify: poly.simplify(0.01, preserve_topology=True). Convex hull: poly.convex_hull. Centroid: poly.centroid → Point. Bounds: poly.bounds → (minx, miny, maxx, maxy). Area: poly.area. Length: line.length. Distance: a.distance(b). Nearest point: shapely.ops.nearest_points(a, b). Unary union: from shapely.ops import unary_union; merged = unary_union([a, b, c]). Affine: from shapely.affinity import translate, rotate, scale; rotate(poly, 45, origin="centroid"). STR-tree: from shapely.strtree import STRtree; tree = STRtree(geoms); tree.query(bbox). Claude Code generates Shapely spatial analyzers, GIS tools, collision detectors, and geometry preprocessors.

CLAUDE.md for Shapely

## Shapely Stack
- Version: shapely >= 2.0 | pip install shapely
- Create: Point(x,y) | LineString(coords) | Polygon(exterior, [holes])
- Load: wkt.loads("WKT") | shape(geojson_dict) | from_wkb(bytes)
- Predicates: contains | within | intersects | touches | crosses | disjoint
- Ops: a.intersection(b) | a.union(b) | a.buffer(r) | a.simplify(tol)
- Measure: .area | .length | .distance(b) | .bounds | .centroid

Shapely Geometry Pipeline

# app/geometry.py — Shapely shapes, predicates, set ops, spatial index, transforms, GeoJSON
from __future__ import annotations

import json
from typing import Any, Generator

from shapely import wkt, wkb
from shapely.affinity import (
    affine_transform,
    rotate,
    scale,
    translate,
)
from shapely.geometry import (
    GeometryCollection,
    LinearRing,
    LineString,
    MultiLineString,
    MultiPoint,
    MultiPolygon,
    Point,
    Polygon,
    mapping,
    shape,
)
from shapely.ops import (
    nearest_points,
    split,
    unary_union,
    polygonize,
    transform,
)
from shapely.strtree import STRtree


# ─────────────────────────────────────────────────────────────────────────────
# 1. Construction helpers
# ─────────────────────────────────────────────────────────────────────────────

def make_point(x: float, y: float, z: float | None = None) -> Point:
    """
    Create a 2D or 3D Point.

    Example:
        p2 = make_point(1.0, 2.0)
        p3 = make_point(1.0, 2.0, 100.0)
    """
    return Point(x, y) if z is None else Point(x, y, z)


def make_bbox(minx: float, miny: float, maxx: float, maxy: float) -> Polygon:
    """
    Create a bounding-box Polygon.

    Example:
        bbox = make_bbox(-122.5, 37.7, -122.3, 37.9)  # SF area
    """
    return Polygon([
        (minx, miny), (maxx, miny),
        (maxx, maxy), (minx, maxy),
        (minx, miny),
    ])


def from_geojson(geojson: dict | str) -> Any:
    """
    Create a Shapely geometry from a GeoJSON dict or string.

    Example:
        geom = from_geojson({"type": "Point", "coordinates": [1.0, 2.0]})
        geom = from_geojson(geojson_string)
    """
    if isinstance(geojson, str):
        geojson = json.loads(geojson)
    if "geometry" in geojson:  # Feature
        geojson = geojson["geometry"]
    return shape(geojson)


def to_geojson(geom) -> dict:
    """Convert Shapely geometry to GeoJSON dict."""
    return mapping(geom)


def from_wkt_str(wkt_str: str):
    """Load geometry from WKT string."""
    return wkt.loads(wkt_str)


def to_wkt_str(geom, rnd: int | None = 6) -> str:
    """Serialize geometry to WKT, rounding coordinates."""
    if rnd is not None:
        return geom.wkt  # Shapely 2 handles precision via set_precision
    return geom.wkt


# ─────────────────────────────────────────────────────────────────────────────
# 2. Spatial predicates
# ─────────────────────────────────────────────────────────────────────────────

def contains(container, item) -> bool:
    """Return True if container fully contains item (no boundary contact)."""
    return container.contains(item)


def intersects_geom(a, b) -> bool:
    """Return True if a and b share any geometry."""
    return a.intersects(b)


def within_distance(a, b, distance: float) -> bool:
    """Return True if the distance between a and b is <= distance."""
    return a.distance(b) <= distance


def classify_relationship(a, b) -> str:
    """
    Classify the DE-9IM spatial relationship between two geometries.
    Returns one of: contains, within, intersects, touches, crosses, disjoint, equals

    Example:
        rel = classify_relationship(polygon, point)  # "contains" or "disjoint"
    """
    if a.equals(b):          return "equals"
    if a.contains(b):        return "contains"
    if a.within(b):          return "within"
    if a.crosses(b):         return "crosses"
    if a.touches(b):         return "touches"
    if a.intersects(b):      return "intersects"
    return "disjoint"


# ─────────────────────────────────────────────────────────────────────────────
# 3. Set operations
# ─────────────────────────────────────────────────────────────────────────────

def intersection(a, b):
    """Return the intersection geometry of a and b."""
    return a.intersection(b)


def union(a, b):
    """Return the union of a and b."""
    return a.union(b)


def merge_geometries(geoms: list) -> MultiPolygon | Polygon | GeometryCollection:
    """
    Merge a list of geometries into a single geometry using unary_union.

    Example:
        block = merge_geometries([parcel1, parcel2, parcel3])
    """
    return unary_union(geoms)


def clip(geom, bbox_or_mask):
    """
    Clip a geometry to a bounding box or mask polygon.

    Example:
        clipped = clip(city_boundary, study_area_polygon)
    """
    return geom.intersection(bbox_or_mask)


def subtract(base, other):
    """Remove other's area from base (difference)."""
    return base.difference(other)


# ─────────────────────────────────────────────────────────────────────────────
# 4. Measurement
# ─────────────────────────────────────────────────────────────────────────────

def area(geom) -> float:
    """Return the area (in coordinate units squared)."""
    return geom.area


def length(geom) -> float:
    """Return the length/perimeter (in coordinate units)."""
    return geom.length


def distance_between(a, b) -> float:
    """Euclidean distance between two geometries."""
    return a.distance(b)


def closest_points(a, b) -> tuple[Point, Point]:
    """
    Return the pair of closest points between two geometries.

    Example:
        p1, p2 = closest_points(line, polygon)
        dist = p1.distance(p2)
    """
    return nearest_points(a, b)


def bounds(geom) -> tuple[float, float, float, float]:
    """Return (minx, miny, maxx, maxy) bounding box."""
    return geom.bounds


def centroid(geom) -> Point:
    """Return the geometric centroid."""
    return geom.centroid


def envelope(geom) -> Polygon:
    """Return the minimum bounding rectangle."""
    return geom.envelope


# ─────────────────────────────────────────────────────────────────────────────
# 5. Transformations
# ─────────────────────────────────────────────────────────────────────────────

def buffer_geom(geom, distance: float, resolution: int = 16, cap_style: str = "round") -> Polygon:
    """
    Buffer a geometry by distance.
    cap_style: "round" | "flat" | "square"
    resolution: segments per quarter circle (higher = smoother).

    Example:
        circle  = buffer_geom(point, 100)          # 100-unit circle
        corridor = buffer_geom(road_line, 5)        # 5-unit road buffer
        expanded = buffer_geom(polygon, -10)        # inset (negative buffer)
    """
    cap_map = {"round": 1, "flat": 2, "square": 3}
    return geom.buffer(distance, resolution=resolution, cap_style=cap_map.get(cap_style, 1))


def simplify_geom(geom, tolerance: float = 1.0, preserve_topology: bool = True):
    """
    Simplify geometry using Douglas-Peucker algorithm.

    Example:
        simple = simplify_geom(complex_coastline, tolerance=0.001)
    """
    return geom.simplify(tolerance, preserve_topology=preserve_topology)


def translate_geom(geom, x: float = 0, y: float = 0, z: float = 0):
    """Translate geometry by (x, y, z) offset."""
    return translate(geom, xoff=x, yoff=y, zoff=z)


def rotate_geom(geom, angle_degrees: float, origin: str | tuple = "centroid"):
    """
    Rotate geometry by angle_degrees around origin.
    origin: "centroid" | "center" | (x, y)

    Example:
        rotated = rotate_geom(polygon, 45)
        rotated_around = rotate_geom(polygon, 90, origin=(0, 0))
    """
    return rotate(geom, angle_degrees, origin=origin)


def scale_geom(geom, x_factor: float = 1.0, y_factor: float = 1.0, origin: str | tuple = "centroid"):
    """Scale geometry by x_factor and y_factor."""
    return scale(geom, xfact=x_factor, yfact=y_factor, origin=origin)


# ─────────────────────────────────────────────────────────────────────────────
# 6. Spatial indexing
# ─────────────────────────────────────────────────────────────────────────────

class SpatialIndex:
    """
    Wrapper around Shapely STRtree for efficient spatial queries.

    Example:
        idx = SpatialIndex(parcels)
        nearby = idx.query_intersects(search_polygon)
        closest = idx.nearest(point, k=5)
    """

    def __init__(self, geometries: list) -> None:
        self._geoms = geometries
        self._tree  = STRtree(geometries)

    def query_intersects(self, geom) -> list:
        """Return all geometries that intersect geom."""
        indices = self._tree.query(geom, predicate="intersects")
        return [self._geoms[i] for i in indices]

    def query_within(self, geom) -> list:
        """Return geometries fully contained within geom."""
        indices = self._tree.query(geom, predicate="within")
        return [self._geoms[i] for i in indices]

    def query_bbox(self, minx: float, miny: float, maxx: float, maxy: float) -> list:
        """Return geometries intersecting a bounding box."""
        bbox = make_bbox(minx, miny, maxx, maxy)
        return self.query_intersects(bbox)

    def nearest(self, geom, k: int = 1) -> list:
        """Return k nearest geometries."""
        indices = self._tree.nearest(geom) if k == 1 else self._tree.query_nearest(geom, max_distance=None, exclusive=False, all_matches=False)
        if not hasattr(indices, "__iter__"):
            indices = [indices]
        return [self._geoms[i] for i in list(indices)[:k]]

    def __len__(self) -> int:
        return len(self._geoms)


# ─────────────────────────────────────────────────────────────────────────────
# 7. GeoJSON collection helpers
# ─────────────────────────────────────────────────────────────────────────────

def to_feature(geom, properties: dict | None = None) -> dict:
    """Wrap a geometry as a GeoJSON Feature."""
    return {"type": "Feature", "geometry": to_geojson(geom), "properties": properties or {}}


def to_feature_collection(geoms: list, properties_list: list[dict] | None = None) -> dict:
    """
    Convert a list of geometries to a GeoJSON FeatureCollection.

    Example:
        fc = to_feature_collection(polygons, [{"id": i} for i in range(len(polygons))])
    """
    props = properties_list or [{} for _ in geoms]
    features = [to_feature(g, p) for g, p in zip(geoms, props)]
    return {"type": "FeatureCollection", "features": features}


# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    print("=== Construction ===")
    pt   = make_point(1.0, 2.0)
    line = LineString([(0, 0), (4, 0), (4, 4)])
    poly = Polygon([(0, 0), (3, 0), (3, 3), (0, 3)])
    bbox = make_bbox(0, 0, 5, 5)
    print(f"  Point:    {pt.wkt}")
    print(f"  Polygon:  area={poly.area:.1f}, perimeter={poly.length:.1f}")
    print(f"  Bounds:   {bbox.bounds}")

    print("\n=== Predicates ===")
    print(f"  poly.contains(pt):      {contains(poly, pt)}")
    print(f"  poly.intersects(line):  {intersects_geom(poly, line)}")
    print(f"  classify:               {classify_relationship(bbox, poly)}")

    print("\n=== Set operations ===")
    poly2 = Polygon([(2, 2), (5, 2), (5, 5), (2, 5)])
    inter = intersection(poly, poly2)
    unio  = union(poly, poly2)
    diff  = subtract(poly, poly2)
    print(f"  Intersection area: {inter.area:.2f}")
    print(f"  Union area:        {unio.area:.2f}")
    print(f"  Difference area:   {diff.area:.2f}")

    print("\n=== Measurements ===")
    print(f"  Distance pt to poly: {distance_between(make_point(5, 5), poly):.2f}")
    p1, p2 = closest_points(make_point(5, 5), poly)
    print(f"  Closest points: {p1.wkt}{p2.wkt}")

    print("\n=== Transforms ===")
    circle = buffer_geom(pt, 2.0)
    print(f"  Circle area (r=2): {circle.area:.2f}")
    simple = simplify_geom(circle, tolerance=0.5)
    print(f"  Simplified coords: {len(list(simple.exterior.coords))}")
    rotated = rotate_geom(poly, 45)
    print(f"  Rotated centroid: {rotated.centroid.wkt}")

    print("\n=== Spatial index ===")
    polys = [buffer_geom(make_point(i, i), 0.6) for i in range(10)]
    idx   = SpatialIndex(polys)
    query_area = make_bbox(1.5, 1.5, 3.5, 3.5)
    found = idx.query_intersects(query_area)
    print(f"  Indexed {len(idx)} circles, found {len(found)} in query bbox")

    print("\n=== GeoJSON round-trip ===")
    fc = to_feature_collection([poly, poly2], [{"id": 1}, {"id": 2}])
    reloaded = [from_geojson(f["geometry"]) for f in fc["features"]]
    print(f"  FeatureCollection features: {len(fc['features'])}")
    print(f"  Reloaded area[0]: {reloaded[0].area:.1f}")

For the GeoPandas alternative — GeoPandas builds on Shapely and Fiona to provide a DataFrame-style interface with .plot() visualization, CRS management via pyproj, and read/write support for Shapefiles, GeoPackages, and PostGIS; Shapely is the pure-geometry computational engine underneath — use Shapely directly when you need geometry operations in non-tabular pipelines (collision detection, API geometry validation, spatial joins in code), GeoPandas when you’re working with geographic datasets that need CRS transformations and tabular analysis. For the pyproj alternative — pyproj handles coordinate reference system (CRS) transformations between geographic and projected systems (WGS84, UTM, Web Mercator); Shapely is CRS-agnostic and works in pure coordinate space — combine Shapely with pyproj’s Transformer when real-world distances (meters) matter; use Shapely alone when your data is already in a consistent projected coordinate system. The Claude Skills 360 bundle includes Shapely skill sets covering make_point()/make_bbox()/from_geojson()/from_wkt_str() construction, contains()/intersects_geom()/within_distance()/classify_relationship() predicates, intersection()/union()/merge_geometries()/clip()/subtract() set ops, area()/length()/distance_between()/closest_points()/bounds()/centroid() measurement, buffer_geom()/simplify_geom()/translate_geom()/rotate_geom()/scale_geom() transforms, SpatialIndex with STRtree, and to_feature_collection() GeoJSON export. Start with the free tier to try spatial geometry and GIS analysis code generation.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free