logo
Blog Image

anygeom-py: Generate Random Geospatial Geometries in Python with One Line of Code

7 min read

By krishnaglodha

open source

Share:

The Problem Every GIS Developer Knows Too Well

Picture this: It’s 2 AM, you’re building a spatial analysis tool, and you need test data. Not just any data — you need 500 random points in UTM Zone 43N, 100 polygons with holes, and 50 circles in Web Mercator projection.

Your options?

  1. Manual Drawing: Open geojson.io, spend 6 hours clicking points on a map
  2. Custom Scripts: Write throwaway code for each test case
  3. Compromise: Settle for EPSG:4326 and hope it’s good enough

I’ve been there. As a GIS developer, I’ve wasted countless hours creating dummy geometries for testing. The frustration of switching between projections, manually drawing complex polygons, and exporting to the right format became a weekly ritual.

There had to be a better way.

Introducing anygeom-py

anygeom-py is a Python package that generates random geospatial geometries in any projection with a single line of code. What used to take hours now takes seconds.

from anygeom import Point, Polygon, Circle

# Generate 500 test points in UTM Zone 43N
points = Point(count=500, crs=32643, bbox=[72.0, 18.0, 73.0, 19.0])
# Create 100 complex polygons with holes
polygons = Polygon(count=100, hole=True, min_vertex=6, max_vertex=12)
# Generate 50 perfect circles in Web Mercator
circles = Circle(count=50, radius=1000, crs=3857)

That’s it. Three lines of code. Three seconds of execution time.

Why This Matters

The Time Savings Are Real

Let’s do the math:

  • Before: 1 hours to create 500 test geometries manually
  • After: 3 seconds with anygeom-py

But it’s not just about time. It’s about:

  • Reproducibility: Same code, same results every time
  • Flexibility: Change projections instantly
  • Scalability: Need 10,000 geometries? No problem
  • Focus: Spend time building features, not creating test data

Real-World Use Cases

Since publishing to PyPI, developers are using anygeom-py for:

1. Testing Spatial Databases

# Generate test data for PostGIS performance testing
test_points = Point(count=10000, crs=4326, bbox=[-180, -90, 180, 90])

2. Building Map Visualizations

# Create demo data for Leaflet/Mapbox applications
demo_polygons = Polygon(count=50, bbox=[68.17, 7.96, 97.40, 35.49])

3. Teaching GIS Programming

# Generate examples for spatial analysis tutorials
student_data = Point(count=100, crs=32643, bbox=[72.8, 18.9, 72.9, 19.0])

4. Prototyping Location Services

# Mock location data for app development
user_locations = Point(count=1000, crs=3857, bbox=[-74.1, 40.6, -73.9, 40.8])

Key Features That Make It Powerful

1. Any Projection, Any Time

Support for all EPSG codes means you can work in your preferred coordinate reference system:

# WGS84 (EPSG:4326)
wgs84_points = Point(count=100, crs=4326)
# Web Mercator (EPSG:3857)
web_mercator_points = Point(count=100, crs=3857)
# UTM Zone 43N (EPSG:32643)
utm_points = Point(count=100, crs=32643)
# Any other EPSG code
custom_crs = Point(count=100, crs=2154)  # Lambert 93 (France)p

2. GeoJSON Ready

Returns proper GeoJSON Features, not just geometries:

point = Point()
print(point.__geo_interface__)

# Output:
# {
#   "type": "Feature",
#   "properties": {},
#   "geometry": {
#     "type": "Point",
#     "coordinates": [34.62, 14.18]
#   }
# }

Multiple geometries return a list of Features:

points = Point(count=3)
print(len(points))  # 3
print(points[0])    # Access individual features

3. Customizable Everything

Bounding Boxes: Define exactly where geometries should appear

# Generate points only in Mumbai
mumbai_points = Point(
    count=100,
    bbox=[72.8, 18.9, 72.9, 19.0]
)

Vertex Control: Specify complexity for lines and polygons

# Simple linestring (2-5 vertices)
simple_line = LineString(min_vertex=2, max_vertex=5)


# Complex linestring (20-50 vertices)
complex_line = LineString(min_vertex=20, max_vertex=50)

Polygon Holes: Create realistic complex polygons

# Polygon with a hole (like a donut)
donut = Polygon(hole=True, min_vertex=8, max_vertex=12)

Circle Precision: Control smoothness

# Rough circle (16 points)
rough_circle = Circle(radius=1000, num_points=16)

# Smooth circle (128 points)
smooth_circle = Circle(radius=1000, num_points=128)

4. Built-in Validation

Clear error messages help you catch mistakes early:

# Invalid vertex range
LineString(min_vertex=40, max_vertex=6)
# ValueError: min_vertex (40) cannot be greater than max_vertex (6)

# Invalid bounding box
Point(bbox=[83.77, 29.12, 72.81, 12.70])
# ValueError: bbox minx (83.77) must be less than maxx (72.81)
# Invalid count
Point(count=0)
# ValueError: count must be at least 1, got 0

5. Shapely Compatible

All geometries are Shapely objects, so you can use the full Shapely API:

from anygeom import Point, Polygon
# Generate a point
point = Point()

# Use Shapely methods
print(point.x, point.y)
buffered = point.buffer(1)
print(buffered.area)
# Generate a polygon
polygon = Polygon()
# Use Shapely properties
print(polygon.area)
print(polygon.bounds)
print(polygon.centroid)

Complete API Reference

Point

Point(count=1, crs=4326, bbox=None)

Generates random point geometries.

LineString

LineString(count=1, crs=4326, bbox=None, min_vertex=2, max_vertex=5)

Generates random linestring geometries with customizable vertex count.

Polygon

Polygon(count=1, crs=4326, bbox=None, min_vertex=3, max_vertex=8, hole=False)

Generates random polygon geometries with optional holes.

Circle

Circle(count=1, crs=4326, bbox=None, radius=None, num_points=64)

Generates circular polygon geometries with customizable radius and smoothness.

Multi-Geometries

MultiPoint(count=2, crs=4326, bbox=None)
MultiLineString(count=2, crs=4326, bbox=None, min_vertex=2, max_vertex=5)
MultiPolygon(count=2, crs=4326, bbox=None, min_vertex=3, max_vertex=8, hole=False)

Generates Multi* geometry types (single feature with multiple geometries).

Practical Examples

Example 1: Testing a Spatial Database

from anygeom import Point, Polygon
import psycopg2
import json

# Connect to PostGIS database
conn = psycopg2.connect("dbname=test user=postgres")
cur = conn.cursor()
# Generate test data
test_points = Point(count=1000, crs=4326, bbox=[-180, -90, 180, 90])
# Insert into database
for point in test_points:
    geojson = json.dumps(point.__geo_interface__)
    cur.execute(
        "INSERT INTO test_points (geom) VALUES (ST_GeomFromGeoJSON(%s))",
        (geojson,)
    )
conn.commit()

Example 2: Creating a Map Visualization

from anygeom import Polygon, Circle
import folium
import json
# Generate demo data
polygons = Polygon(count=20, bbox=[72.8, 18.9, 72.9, 19.0])
circles = Circle(count=10, radius=0.01, bbox=[72.8, 18.9, 72.9, 19.0])
# Create map
m = folium.Map(location=[18.95, 72.85], zoom_start=12)
# Add polygons
for poly in polygons:
    folium.GeoJson(poly.__geo_interface__).add_to(m)
# Add circles
for circle in circles:
    folium.GeoJson(circle.__geo_interface__).add_to(m)
m.save('demo_map.html')

Example 3: Teaching Spatial Analysis

from anygeom import Point
import geopandas as gpd

# Generate student exercise data
points = Point(count=100, crs=4326, bbox=[72.8, 18.9, 72.9, 19.0])
# Convert to GeoDataFrame
features = [p.__geo_interface__ for p in points]
gdf = gpd.GeoDataFrame.from_features(features)
# Students can now practice spatial operations
print(gdf.head())
print(gdf.total_bounds)
print(gdf.distance(gdf.iloc[0].geometry))

Example 4: Benchmarking Spatial Algorithms

from anygeom import Point, Polygon
import time

# Generate large dataset
points = Point(count=10000, crs=4326, bbox=[-180, -90, 180, 90])
polygons = Polygon(count=1000, crs=4326, bbox=[-180, -90, 180, 90])
# Benchmark spatial join
start = time.time()
# ... your spatial join code ...
end = time.time()
print(f"Spatial join took {end - start:.2f} seconds")

Technical Architecture

The Challenge

Building anygeom-py required solving several technical challenges:

1. Coordinate Reference Systems

  • EPSG:3857 (Web Mercator) has latitude limits (-85° to 85°)
  • Coordinate transformations can produce NaN values
  • Different CRS have different valid bounds

2. GeoJSON Compliance

  • Shapely returns tuples for coordinates
  • GeoJSON spec requires lists
  • Need to maintain Shapely compatibility

3. Flexible Return Types

  • Single geometry should return a Feature
  • Multiple geometries should return a list of Features
  • Must support iteration and indexing

The Solution

Wrapper Pattern: Created two wrapper classes that provide GeoJSON Feature output while maintaining Shapely compatibility:

class _GeometryWrapper:
    """Wraps single Shapely geometry"""
    def __init__(self, geom):
        self._geom = geom
    
    @property
    def __geo_interface__(self):
        return {
            "type": "Feature",
            "properties": {},
            "geometry": json.loads(json.dumps(self._geom.__geo_interface__))
        }

class _GeometryListWrapper:
    """Wraps multiple geometries"""
    def __init__(self, geoms):
        self._geoms = geoms
    
    @property
    def __geo_interface__(self):
        return [_to_feature(g) for g in self._geoms]

Factory Pattern: Used __new__ to return appropriate wrapper types:

class Point:
    def __new__(cls, count=1, crs=4326, bbox=None):
        if count == 1:
            return _GeometryWrapper(geometry.Point(...))
        return _GeometryListWrapper([geometry.Point(...) for _ in range(count)])

CRS-Aware Defaults: Implemented smart bbox defaults to prevent transformation errors:

def _get_default_bbox(crs):
    if crs == 3857:  # Web Mercator
        return [-180, -85, 180, 85]
    return [-180, -90, 180, 90]

Installation and Getting Started

Installation

pip install anygeom-py

Quick Start

from anygeom import Point

# Generate a single point
point = Point()
# Generate multiple points
points = Point(count=10)
# Generate with custom parameters
custom_points = Point(
    count=100,
    crs=32643,
    bbox=[72.0, 18.0, 73.0, 19.0]
)
# Export as GeoJSON
geojson = custom_points.__geo_interface__
# Save to file
import json
with open('points.geojson', 'w') as f:
    json.dump(geojson, f, indent=2)

Roadmap and Future Features

Based on community feedback, here’s what’s coming:

Version 0.2.0 (Next Release)

  • Rectangle/Box geometry
  • Ellipse geometry
  • Triangle geometry
  • Regular polygons (Pentagon, Hexagon, Octagon)

Version 0.3.0

  • 3D geometry support (Z coordinates)
  • Custom properties in GeoJSON Features
  • Geometry constraints (non-overlapping, minimum distance)
  • Export to WKT and WKB formats

Version 0.4.0

  • Realistic geometry patterns (road networks, building footprints)
  • Geometry clustering algorithms
  • CLI tool for quick generation
  • Performance optimizations for large datasets

Contributing and Feedback

anygeom-py is open source (Apache-2.0 license) and welcomes contributions!

Ways to Contribute

  1. Report Bugs: Found an issue? Open a GitHub issue
  2. Request Features: What geometry types would you like?
  3. Submit PRs: Code contributions are welcome
  4. Share Use Cases: How are you using anygeom-py?
  5. Spread the Word: Star the repo, share with colleagues

Beta Testing

I’m actively seeking beta testers! If you:

  • Work with geospatial data
  • Build GIS applications
  • Teach spatial programming
  • Need test data regularly

Try anygeom-py and share your feedback. Your input shapes the roadmap.

Get Started Today

pip install anygeom-py

Links

Try It Now

from anygeom import Point, Polygon, Circle

# Your first geometry in 3 seconds
points = Point(count=100, crs=4326, bbox=[-180, -90, 180, 90])
print(f"Generated {len(points)} points!")

If you found this useful, please give the project a star on GitHub and share it with your network. Let’s make GIS development more efficient together!

Share:

Related Articles

Image

Unlock Exclusive Content and Stay updated.

Subscribe today!

Interesting content are in store for you.

What are you interested to know more about?