Skip to content

feat: Implement Geometry Server operations (buffer, simplify, project/transform) #98

@mikemcdougall

Description

@mikemcdougall

Summary

Implement core Geometry Server operations for spatial analysis and geometry processing using PostGIS functions.

Background

From the roadmap (docs/ROADMAP.md):

GeometryServer basics: project, buffer, simplify.

These operations are fundamental for geospatial analysis and are commonly used in GIS applications. PostGIS provides efficient implementations that should be exposed through REST API endpoints.

Proposed Operations

1. Buffer Operations

Create buffer zones around geometries - critical for proximity analysis.

Endpoint: POST /rest/services/geometry/buffer

Use Cases:

  • Environmental impact zones (500m around industrial sites)
  • Service areas (1km around schools, hospitals)
  • Transportation corridors (50m buffer around highways)
  • Property setbacks and zoning analysis

2. Simplify Operations

Reduce geometry complexity while preserving essential shape characteristics.

Endpoint: POST /rest/services/geometry/simplify

Use Cases:

  • Optimize geometries for web display at different zoom levels
  • Reduce file sizes for data export
  • Improve query performance on complex geometries
  • Cartographic generalization

3. Project/Transform Operations

Convert geometries between coordinate reference systems.

Endpoint: POST /rest/services/geometry/project

Use Cases:

  • Convert GPS coordinates (WGS84) to local projected coordinates
  • Reproject imported data to match layer CRS
  • Convert between different national grid systems
  • Prepare data for area/distance calculations

API Design

Buffer Request/Response

POST /rest/services/geometry/buffer
{
  "geometries": [
    {"x": -122.4194, "y": 37.7749}
  ],
  "inSR": 4326,
  "outSR": 4326,
  "distances": [1000],
  "unit": "esriMeters",
  "unionResults": false,
  "geodesic": true
}

Response:
{
  "geometries": [
    {"rings": [[...]]}
  ],
  "spatialReference": {"wkid": 4326}
}

Simplify Request/Response

POST /rest/services/geometry/simplify
{
  "geometries": [
    {"rings": [[...]]}
  ],
  "inSR": 4326,
  "outSR": 4326,
  "maxDeviation": 10,
  "deviationUnit": "esriMeters"
}

Project Request/Response

POST /rest/services/geometry/project
{
  "geometries": [
    {"x": -122.4194, "y": 37.7749}
  ],
  "inSR": 4326,
  "outSR": 3857,
  "transformation": null
}

Acceptance Criteria

Buffer Operations

  • Point buffer creates circular polygon (using geography for geodesic)
  • Polygon buffer creates expanded/contracted polygon
  • Line buffer creates corridor polygon
  • Multiple distance values create multiple buffer zones
  • Union option combines overlapping buffers
  • Geodesic vs planar buffer options work correctly

Simplify Operations

  • Douglas-Peucker algorithm via ST_Simplify
  • Preserve topology option via ST_SimplifyPreserveTopology
  • Tolerance specified in various units (meters, degrees, feet)
  • Works with all geometry types (point, line, polygon)
  • Simplified geometries remain valid

Project/Transform Operations

  • Transform between any EPSG coordinate systems
  • Batch transformation of multiple geometries
  • Automatic datum transformation when needed
  • Error handling for unsupported CRS combinations
  • Performance optimization for same-CRS requests (no-op)

Error Handling

  • Invalid geometry input returns descriptive errors
  • Unsupported CRS returns proper error codes
  • Buffer distance validation (positive values)
  • Simplify tolerance validation (reasonable ranges)

Technical Implementation

PostGIS Function Mapping

public class GeometryService
{
    // Buffer - use geography type for geodesic buffers
    public async Task<byte[]> BufferAsync(byte[] wkb, double distance, bool geodesic)
    {
        var sql = geodesic
            ? "SELECT ST_AsBinary(ST_Buffer(ST_GeomFromWKB(@wkb)::geography, @distance)::geometry)"
            : "SELECT ST_AsBinary(ST_Buffer(ST_GeomFromWKB(@wkb), @distance))";
        // ...
    }

    // Simplify - with topology preservation option
    public async Task<byte[]> SimplifyAsync(byte[] wkb, double tolerance, bool preserveTopology)
    {
        var function = preserveTopology ? "ST_SimplifyPreserveTopology" : "ST_Simplify";
        var sql = $"SELECT ST_AsBinary({function}(ST_GeomFromWKB(@wkb), @tolerance))";
        // ...
    }

    // Transform - leverage PostGIS CRS database
    public async Task<byte[]> TransformAsync(byte[] wkb, int fromSrid, int toSrid)
    {
        if (fromSrid == toSrid) return wkb; // No-op optimization
        var sql = "SELECT ST_AsBinary(ST_Transform(ST_SetSRID(ST_GeomFromWKB(@wkb), @fromSrid), @toSrid))";
        // ...
    }
}

Endpoint Controllers

[HttpPost("buffer")]
public async Task<IResult> BufferAsync([FromBody] BufferRequest request)
{
    var results = new List<EsriGeometry>();

    foreach (var geometry in request.Geometries)
    {
        var wkb = _converter.ConvertEsriJsonToWkb(JsonSerializer.Serialize(geometry));
        var buffered = await _geometryService.BufferAsync(wkb, request.Distance, request.Geodesic);
        var esriGeom = _converter.ConvertWkbToEsriJson(buffered);
        results.Add(esriGeom);
    }

    return Results.Ok(new BufferResponse { Geometries = results });
}

Performance Considerations

  • Batch operations - Process multiple geometries efficiently
  • Streaming results - Don't buffer large result sets in memory
  • Caching - Cache transformation parameters for repeated CRS conversions
  • Limits - Impose reasonable limits on input geometry complexity
  • Timeouts - Set operation timeouts to prevent DoS

Test Plan

Unit Tests

  • PostGIS function calls generate correct SQL
  • Parameter validation rejects invalid inputs
  • Batch processing works correctly
  • Error handling covers edge cases

Integration Tests

  • Buffer around point creates expected circular area
  • Buffer around line creates expected corridor
  • Simplify reduces vertex count while preserving shape
  • Transform between common CRS pairs (WGS84 ↔ Web Mercator)
  • Batch operations process multiple geometries
  • Error responses include helpful messages

Performance Tests

  • Buffer operations complete within reasonable time
  • Simplify operations reduce complexity as expected
  • Transform operations handle large geometry sets
  • Memory usage remains bounded during processing

Real-World Use Cases

Environmental Planning

// Create 500m buffer around industrial sites
const bufferRequest = {
  geometries: industrialSites,
  distances: [500],
  unit: "esriMeters",
  geodesic: true
};

Web Mapping Optimization

// Simplify complex coastline for web display
const simplifyRequest = {
  geometries: [coastlineGeometry],
  maxDeviation: zoomLevel < 8 ? 100 : 10,
  deviationUnit: "esriMeters"
};

Coordinate System Conversion

// Convert GPS coordinates to state plane
const projectRequest = {
  geometries: gpsPoints,
  inSR: 4326,      // WGS84
  outSR: 2154      // California State Plane
};

Dependencies

Legacy Reference

  • ../Honua.Server/src/platform/core/Geometry/ - geometry operation patterns
  • Esri Geometry Service documentation for API compatibility
  • PostGIS documentation for ST_Buffer, ST_Simplify, ST_Transform

Future Enhancements

  • Additional operations: Union, Intersection, Difference
  • Batch optimization: Spatial index-based processing
  • Caching: Cache common transformation results
  • Async processing: Queue long-running operations

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions