Skip to main content

Pydantic validation for GeoJson

Project description

GitHub Actions status for master branch Latest PyPI package version codecov Downloads

pydantic-geojson 🌍

A type-safe, Pydantic-based library for validating and working with GeoJSON data according to RFC 7946 specification.

Supported GeoJSON Objects

GeoJSON Objects Status Description
Point A single geographic coordinate
MultiPoint Multiple points
LineString A sequence of connected points forming a line
MultiLineString Multiple line strings
Polygon A closed area, optionally with holes
MultiPolygon Multiple polygons
GeometryCollection Collection of different geometry types
Feature Geometry with properties
FeatureCollection Collection of features

Installation

pydantic-geojson is compatible with Python 3.9 and up.

The recommended way to install is via poetry:

poetry add pydantic_geojson

Using pip to install is also possible:

pip install pydantic_geojson

Quick Start

from pydantic_geojson import PointModel

# Create a point
point = PointModel(
    type="Point",
    coordinates=[-105.01621, 39.57422]
)

print(point)
# type='Point' coordinates=Coordinates(lon=-105.01621, lat=39.57422)

GeoJSON Types with Visualizations

Type Visualization Usage
Point
A single geographic coordinate
Point
from pydantic_geojson import PointModel

data = {
    "type": "Point",
    "coordinates": [-105.01621, 39.57422]
}
point = PointModel(**data)
MultiPoint
Multiple points
MultiPoint
from pydantic_geojson import MultiPointModel

data = {
    "type": "MultiPoint",
    "coordinates": [
        [-105.01621, 39.57422],
        [-80.666513, 35.053994]
    ]
}
multi_point = MultiPointModel(**data)
LineString
A sequence of connected points forming a line
LineString
from pydantic_geojson import LineStringModel

data = {
    "type": "LineString",
    "coordinates": [
        [-99.113159, 38.869651],
        [-99.0802, 38.85682],
        [-98.822021, 38.85682],
        [-98.448486, 38.848264]
    ]
}
line_string = LineStringModel(**data)
MultiLineString
Multiple line strings
MultiLineString
from pydantic_geojson import MultiLineStringModel

data = {
    "type": "MultiLineString",
    "coordinates": [
        [[-105.019898, 39.574997],
         [-105.019598, 39.574898],
         [-105.019061, 39.574782]],
        [[-105.017173, 39.574402],
         [-105.01698, 39.574385],
         [-105.016636, 39.574385]]
    ]
}
multi_line_string = MultiLineStringModel(**data)
Polygon
A closed area, optionally with holes
Polygon
from pydantic_geojson import PolygonModel

data = {
    "type": "Polygon",
    "coordinates": [
        [[100, 0],
         [101, 0],
         [101, 1],
         [100, 1],
         [100, 0]]
    ]
}
polygon = PolygonModel(**data)
MultiPolygon
Multiple polygons
MultiPolygon
from pydantic_geojson import MultiPolygonModel

data = {
    "type": "MultiPolygon",
    "coordinates": [
        [[[107, 7], [108, 7],
          [108, 8], [107, 8],
          [107, 7]]],
        [[[100, 0], [101, 0],
          [101, 1], [100, 1],
          [100, 0]]]
    ]
}
multi_polygon = MultiPolygonModel(**data)
GeometryCollection
Collection of different geometry types
GeometryCollection
from pydantic_geojson import GeometryCollectionModel

data = {
    "type": "GeometryCollection",
    "geometries": [
        {
            "type": "Point",
            "coordinates": [-80.660805, 35.049392]
        },
        {
            "type": "Polygon",
            "coordinates": [[[-80.664582, 35.044965],
                             [-80.663874, 35.04428],
                             [-80.662586, 35.04558],
                             [-80.663444, 35.046036],
                             [-80.664582, 35.044965]]]
        },
        {
            "type": "LineString",
            "coordinates": [[-80.662372, 35.059509],
                            [-80.662693, 35.059263],
                            [-80.662844, 35.05893]]
        }
    ]
}
geometry_collection = GeometryCollectionModel(**data)

Features and FeatureCollections

Feature

A geometry with properties.

from pydantic_geojson import FeatureModel

data = {
    "type": "Feature",
    "properties": {
        "name": "Dinagat Islands",
        "population": 10000
    },
    "geometry": {
        "type": "Polygon",
        "coordinates": [
            [
                [-80.724878, 35.265454],
                [-80.722646, 35.260338],
                [-80.720329, 35.260618],
                [-80.71681, 35.255361],
                [-80.704793, 35.268397],
                [-80.724878, 35.265454]
            ]
        ]
    }
}

feature = FeatureModel(**data)

FeatureCollection

A collection of features.

from pydantic_geojson import FeatureCollectionModel

data = {
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "geometry": {
                "type": "Point",
                "coordinates": [-80.870885, 35.215151]
            },
            "properties": {
                "name": "Location 1"
            }
        },
        {
            "type": "Feature",
            "geometry": {
                "type": "Polygon",
                "coordinates": [
                    [
                        [-80.724878, 35.265454],
                        [-80.722646, 35.260338],
                        [-80.720329, 35.260618],
                        [-80.704793, 35.268397],
                        [-80.724878, 35.265454]
                    ]
                ]
            },
            "properties": {
                "name": "Location 2"
            }
        }
    ]
}

feature_collection = FeatureCollectionModel(**data)

Custom Properties Models

You can define typed properties models for type-safe feature properties:

from pydantic import BaseModel
from pydantic_geojson import FeatureModel


class CityProperties(BaseModel):
    name: str
    population: int
    country: str


class CityFeature(FeatureModel):
    properties: CityProperties


data = {
    "type": "Feature",
    "properties": {
        "name": "New York",
        "population": 8336817,
        "country": "USA"
    },
    "geometry": {
        "type": "Point",
        "coordinates": [-74.006, 40.7128]
    }
}

city = CityFeature(**data)
print(city.properties.name)  # "New York"
print(city.properties.population)  # 8336817

Validation

pydantic-geojson automatically validates:

  • Coordinate ranges: Longitude must be between -180 and 180, latitude between -90 and 90
  • Geometry types: Ensures correct type strings according to RFC 7946
  • Structure: Validates GeoJSON object structure and required fields
  • Type safety: Full type checking with Pydantic

Example: Invalid Coordinates

from pydantic_geojson import PointModel
from pydantic import ValidationError

try:
    point = PointModel(
        type="Point",
        coordinates=[200, 50]  # Invalid longitude (> 180)
    )
except ValidationError as e:
    print(e)
    # 1 validation error for PointModel
    # coordinates.0
    #   Input should be less than or equal to 180 [type=less_than_equal, input_value=200, input_type=int]

FastAPI Integration

pydantic-geojson works seamlessly with FastAPI for automatic API documentation and OpenAPI schema generation. FastAPI automatically generates interactive API documentation (Swagger UI) with proper GeoJSON schemas.

Basic Example

from fastapi import FastAPI
from pydantic_geojson import FeatureCollectionModel, FeatureModel

app = FastAPI()


@app.post("/features", response_model=FeatureModel)
async def create_feature(feature: FeatureModel):
    """Create a new GeoJSON feature."""
    # Your business logic here
    return feature


@app.get("/features", response_model=FeatureCollectionModel)
async def get_features():
    """Get all features as a FeatureCollection."""
    return FeatureCollectionModel(
        type="FeatureCollection",
        features=[
            FeatureModel(
                type="Feature",
                geometry={
                    "type": "Point",
                    "coordinates": [-105.01621, 39.57422]
                }
            )
        ]
    )

Working with Different Geometry Types

You can use any GeoJSON geometry type in your FastAPI endpoints:

from fastapi import FastAPI
from pydantic_geojson import (
    PointModel,
    PolygonModel,
    LineStringModel,
    FeatureModel,
    FeatureCollectionModel
)

app = FastAPI()


@app.post("/points", response_model=PointModel)
async def create_point(point: PointModel):
    """Create a Point geometry."""
    return point


@app.post("/polygons", response_model=PolygonModel)
async def create_polygon(polygon: PolygonModel):
    """Create a Polygon geometry."""
    return polygon


@app.post("/linestrings", response_model=LineStringModel)
async def create_linestring(linestring: LineStringModel):
    """Create a LineString geometry."""
    return linestring

Custom Properties with FastAPI

Define typed properties models for type-safe feature properties in your API:

from fastapi import FastAPI
from pydantic import BaseModel
from pydantic_geojson import FeatureModel, FeatureCollectionModel

app = FastAPI()


class CityProperties(BaseModel):
    name: str
    population: int
    country: str
    area_km2: float


class CityFeature(FeatureModel):
    properties: CityProperties


@app.post("/cities", response_model=CityFeature)
async def create_city(city: CityFeature):
    """Create a city feature with typed properties."""
    # Access typed properties
    print(f"City: {city.properties.name}")
    print(f"Population: {city.properties.population}")
    return city


@app.get("/cities", response_model=FeatureCollectionModel)
async def get_cities():
    """Get all cities as a FeatureCollection."""
    return FeatureCollectionModel(
        type="FeatureCollection",
        features=[
            CityFeature(
                type="Feature",
                properties=CityProperties(
                    name="New York",
                    population=8336817,
                    country="USA",
                    area_km2=783.8
                ),
                geometry={
                    "type": "Point",
                    "coordinates": [-74.006, 40.7128]
                }
            )
        ]
    )

OpenAPI Schema Generation

FastAPI automatically generates OpenAPI schemas for all pydantic-geojson models. The generated schemas include:

  • Proper GeoJSON structure - All geometry types, Features, and FeatureCollections
  • Coordinate validation - Longitude (-180 to 180) and latitude (-90 to 90) constraints
  • Type definitions - Complete type information for all fields
  • Example values - Sample data in the documentation

Access the interactive API documentation at:

  • Swagger UI: http://localhost:8000/docs
  • ReDoc: http://localhost:8000/redoc
  • OpenAPI JSON: http://localhost:8000/openapi.json

Example API with Full CRUD Operations

from fastapi import FastAPI, HTTPException
from pydantic_geojson import FeatureModel, FeatureCollectionModel
from typing import List

app = FastAPI()

# In-memory storage (use a database in production)
features_db: List[FeatureModel] = []


@app.post("/features", response_model=FeatureModel, status_code=201)
async def create_feature(feature: FeatureModel):
    """Create a new GeoJSON feature."""
    features_db.append(feature)
    return feature


@app.get("/features", response_model=FeatureCollectionModel)
async def list_features():
    """List all features as a FeatureCollection."""
    return FeatureCollectionModel(
        type="FeatureCollection",
        features=features_db
    )


@app.get("/features/{feature_id}", response_model=FeatureModel)
async def get_feature(feature_id: int):
    """Get a specific feature by ID."""
    if feature_id >= len(features_db):
        raise HTTPException(status_code=404, detail="Feature not found")
    return features_db[feature_id]


@app.put("/features/{feature_id}", response_model=FeatureModel)
async def update_feature(feature_id: int, feature: FeatureModel):
    """Update a specific feature."""
    if feature_id >= len(features_db):
        raise HTTPException(status_code=404, detail="Feature not found")
    features_db[feature_id] = feature
    return feature


@app.delete("/features/{feature_id}", status_code=204)
async def delete_feature(feature_id: int):
    """Delete a specific feature."""
    if feature_id >= len(features_db):
        raise HTTPException(status_code=404, detail="Feature not found")
    features_db.pop(feature_id)

Compatibility with Other Libraries

pydantic-geojson is designed to work well with popular Python geospatial libraries:

Pydantic

Built on Pydantic, so you get all the benefits:

  • Type validation
  • JSON serialization/deserialization
  • Model configuration
  • Field validation

FastAPI

Perfect integration for building GeoJSON APIs with automatic OpenAPI documentation.

GeoPandas

You can convert between pydantic-geojson and GeoPandas:

import geopandas as gpd
from pydantic_geojson import FeatureCollectionModel

# Convert FeatureCollection to GeoDataFrame
feature_collection = FeatureCollectionModel(...)
geojson_dict = feature_collection.model_dump()
gdf = gpd.GeoDataFrame.from_features(geojson_dict["features"])

# Convert GeoDataFrame to FeatureCollection
features = [
    FeatureModel(**feature)
    for feature in gdf.to_dict("records")
]

Shapely

Convert to/from Shapely geometries:

from shapely.geometry import Point as ShapelyPoint
from pydantic_geojson import PointModel

# Pydantic GeoJSON to Shapely
pydantic_point = PointModel(type="Point", coordinates=[-105.01621, 39.57422])
shapely_point = ShapelyPoint(
    pydantic_point.coordinates.lon,
    pydantic_point.coordinates.lat
)

# Shapely to Pydantic GeoJSON
shapely_geom = ShapelyPoint(-105.01621, 39.57422)
pydantic_point = PointModel(
    type="Point",
    coordinates=[shapely_geom.x, shapely_geom.y]
)

Testing

Run the test suite:

poetry run pytest

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Links

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pydantic_geojson-0.3.2.tar.gz (17.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pydantic_geojson-0.3.2-py3-none-any.whl (19.9 kB view details)

Uploaded Python 3

File details

Details for the file pydantic_geojson-0.3.2.tar.gz.

File metadata

  • Download URL: pydantic_geojson-0.3.2.tar.gz
  • Upload date:
  • Size: 17.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pydantic_geojson-0.3.2.tar.gz
Algorithm Hash digest
SHA256 b6079978430199b6883539c0ccbc45fcbf38c6b885bd0e60781da599efbec202
MD5 5517653b3016bb43c9877fd87cd4c680
BLAKE2b-256 2cc0f445b00c0f664a6dd5a60cbe91077801f1cc70946fe38e289c349dd904e2

See more details on using hashes here.

Provenance

The following attestation bundles were made for pydantic_geojson-0.3.2.tar.gz:

Publisher: publish.yml on gb-libs/pydantic-geojson

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pydantic_geojson-0.3.2-py3-none-any.whl.

File metadata

File hashes

Hashes for pydantic_geojson-0.3.2-py3-none-any.whl
Algorithm Hash digest
SHA256 714b74eafe4737809e979b7eb8806926ece3675c9f783ffdd4d93f2499ae0270
MD5 e5f8960cfff720192ecbeb51da2611f4
BLAKE2b-256 7cfe184cf565b3c6b63c4647660f51fca562d89691220651c920c0402ba8ed65

See more details on using hashes here.

Provenance

The following attestation bundles were made for pydantic_geojson-0.3.2-py3-none-any.whl:

Publisher: publish.yml on gb-libs/pydantic-geojson

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page