Simple Features 2D Geometry Library for Go

repository·master·Indexed 41 days ago

https://github.com/peterstace/simplefeatures

A 2D geometry library for Go implementing the OpenGIS Simple Feature Access Specification. It provides native Go packages for geometry types and algorithms (geom), cartographic map projections (carto), and spatial indexing (rtree), as well as CGO wrappers for GEOS and PROJ for extended functionality.

Tokens
5.8K
Snippets
12
Records
25
Agent score
51%

What's inside Simple Features

  1. Overview of Simple Features

    master

    Simple Features is a 2D geometry library providing Go types that model geometries and algorithms that operate on them. It is a pure Go implementation of the OpenGIS Consortium's Simple Feature Access Specification, making its API familiar to users of GEOS, JTS, and PostGIS.

    The library is divided into two main categories:

    1. Native Go Packages: Pure Go implementations that do not require CGO.
    2. C-Wrapper Packages: Packages that require CGO and external C libraries for extended functionality.
  2. Overview of the `carto` package

    master

    The carto package provides cartography functionality for working with and creating maps. Its primary capabilities include:

    • Projections: Converting between angular coordinates (longitude and latitude) and planar coordinates (x and y).
    • Earth Radius Definitions: Providing various definitions for the radius of the Earth used in calculations.

    For a complete API reference, consult the godoc documentation.

  3. Implement Static Members in Go

    master

    Static fields in Java become package-level const or var in Go. Static methods become package-level functions. Use the naming pattern JavaPackage_ClassName_MemberName.

    For complex static field initialization, use Immediately Invoked Function Expressions (IIFEs) instead of init() or helper functions to maintain order and clarity.

    package jts
    
    const Math_MathUtils_PI = 3.14159
    var util_Counter_globalCount = 0
    
    func Math_MathUtils_CircleArea(radius float64) float64 {
        return Math_MathUtils_PI * radius * radius
    }
    
    // Complex initialization using IIFE
    var Util_Counter_default = func() *Util_Counter {
        return Util_NewCounter()
    }()
  4. Implement Java-style polymorphism in Go using the child-chain dispatch pattern

    master

    To replicate Java's class-based polymorphism in Go, use a child-chain dispatch pattern. This involves a linked list of pointers where each level of the hierarchy stores a reference to its immediate child.

    Structural Rules

    1. Child Field: Every type must have a child any field (nil for leaf types).
    2. GetChild Method: Every type must implement GetChild() any { return x.child }.
    3. Embedding: Child types must embed pointers to parent types (e.g., *Parent, not Parent).
    4. Leaf Access: Use java.GetSelf(obj) to retrieve the actual leaf type of an object.
    5. Type Checks: Use java.InstanceOf[T](obj) for runtime type checks to respect the inheritance hierarchy.

    Method Dispatch and Implementation

    • Dispatchers (MethodName()): Defined once at the level where the method is introduced. They use java.GetSelf(obj) to find the implementation.
    • Implementations (MethodName_BODY()): Provide the actual logic. These can be defined at any level to provide or override behavior.
    • Overriding: To override a method, define only the MethodName_BODY() implementation. Do not redefine the dispatcher.
    • Introducing New Methods: Define both the dispatcher and the MethodName_BODY() implementation.
    • Abstract Methods: The dispatcher should panic, and no _BODY() should exist.
    • Super Calls: Use c.Parent.MethodName_BODY(args) to call a parent's implementation.
    // BASE CLASS
    type PaymentMethod struct {
        child any
    }
    
    func (p *PaymentMethod) GetChild() any { return p.child }
    
    // Abstract method dispatcher
    func (p *PaymentMethod) Process(amount float64) bool {
        if impl, ok := java.GetSelf(p).(interface{ Process_BODY(float64) bool }); ok {
            return impl.Process_BODY(amount)
        }
        panic("abstract method called")
    }
    
    // INTERMEDIATE CLASS
    type CardPayment struct {
        *PaymentMethod
        child      any
        CardNumber string
    }
    
    func (c *CardPayment) GetChild() any { return c.child }
    
    // Override: define only _BODY, dispatcher inherited from parent
    func (c *CardPayment) Process_BODY(amount float64) bool {
        return c.VerifySecurityCode()
    }
    
    // LEAF CLASS
    type CreditCard struct {
        *CardPayment
        child          any
        CreditLimit    float64
        CurrentBalance float64
    }
    
    func (c *CreditCard) GetChild() any { return c.child }
    
    // Constructor wires up the child chain
    func NewCreditCard(cardNumber string, creditLimit, currentBalance float64) *CreditCard {
        pm := &PaymentMethod{}
        cp := &CardPayment{PaymentMethod: pm, CardNumber: cardNumber}
        cc := &CreditCard{CardPayment: cp, CreditLimit: creditLimit, CurrentBalance: currentBalance}
        pm.child = cp
        cp.child = cc
        return cc
    }
  5. Use the native Go `carto` package

    master

    The carto package provides cartographic map projections and related functionality. It serves as a lightweight replacement for PROJ for simple cartographic projections.

    Supported Projections:

    • Conic: Lambert conformal conic, Albers equal area conic, equidistant conic.
    • Cylindrical: equirectangular, web mercator, Lambert cylindrical equal area.
    • Azimuthal: orthographic, azimuthal equidistant.
    • Pseudocylindrical: sinusoidal.
    • UTM: All 60 UTM projections.
  6. Use the native Go `geom` package

    master

    The geom package provides core geometry types and algorithms without requiring CGO. It is a Go port of JTS.

    Supported Geometry Types:

    • Point, MultiPoint
    • LineString, MultiLineString
    • Polygon, MultiPolygon
    • GeometryCollection

    Key Operations:

    • Serialization: WKT, WKB, GeoJSON, TWKB.
    • Overlay: union, intersection, difference, symmetric difference, unary union.
    • Relate: DE-9IM matrix, equals, intersects, disjoint, contains, covered by, covers, overlaps, touches, within, crosses.
    • Measurements: area, length, distance.
    • Analysis: centroid, convex hull, envelope, point on surface, boundary, minimum area bounding rectangle, minimum width bounding rectangle.
    • Transformations: simplify, densify, snap to grid, reverse, force clockwise, force counter-clockwise, affine transformation.
    • Buffer: configurable end cap styles, join styles, and single-sided mode.
    • Prepared Geometries: Preprocessing for efficient repeated spatial predicate evaluation.
    • Linear Interpolation: Interpolate point, interpolate evenly spaced points.
    • Envelope Operations: intersects, contains, covers, distance, expand to include, center, width, height, area, bounding diagonal.
  7. Handle Pointers and Constructors in Transliterated Code

    master

    Pointers vs Values

    Always use pointers for structs that map to Java classes to maintain Java's reference semantics (mutability, identity, and nil/null behavior).

    • Constructors must return *ClassName.
    • All method receivers must use *ClassName.
    • Struct fields storing references must use *ClassName.

    Constructors

    Since Go does not support method overloading, use multiple functions with descriptive names. Implement constructor chaining where Java does.

    Naming Patterns:

    • NewClassName()
    • NewClassNameWithX(x)
    • NewClassNameFromY(y)
    // Default constructor
    func NewAccount() *Account {
        return NewAccountWithBalance(0.0)
    }
    
    // Constructor with balance (chains to full constructor)
    func NewAccountWithBalance(balance float64) *Account {
        return NewAccountWithBalanceAndCurrency(balance, "USD")
    }
    
    // Full constructor
    func NewAccountWithBalanceAndCurrency(balance float64, currency string) *Account {
        return &Account{balance: balance, currency: currency}
    }
  8. Install GEOS C library for the `geos` package

    master

    The geos package is a thin CGO wrapper around the GEOS library, providing functionality not yet implemented in the native geom package. You must install the GEOS C library on your system first.

    # Debian/Ubuntu
    apt-get install libgeos-dev
    
    # Alpine
    apk add geos-dev
    
    # macOS
    brew install geos
  9. Rules for Java to Go Transliteration

    master

    When porting code from Java to Go within the jtsport context, follow these strict requirements to ensure structural correspondence and verifiability:

    1. 1-1 Mapping: Each Java class maps to exactly one Go struct. Java interfaces map to Go interfaces. Each Java file must map to exactly one Go file (and vice versa). Do not combine multiple Java files into one Go file.
    2. Preserve Element Order: The order of fields, constants, methods, and inner classes in the Go file must match the Java source to enable side-by-side manual review. Only deviate if Go language constraints (like forward references) make it impossible.
    3. Behavioral Equivalence: Go code and its tests must behave identically to the Java source. Do not create Go tests if there are no corresponding Java tests.
    4. No Shortcuts: Do not replace Java helper methods with Go standard library functions. Transliterate the Java method directly (e.g., implement a custom string repeater instead of using strings.Repeat) to maintain structural correspondence.
  10. Handle floating-point precision and math functions

    master

    When porting Java code that requires specific rounding or integer math behavior, use the following guidelines:

    Floating-Point Precision (strictfp)

    To replicate Java's strictfp behavior and prevent Go's FMA optimizations from affecting rounding, wrap every floating-point operation in float64() to force rounding:

    // Java: c = ((a*b - C) + d*e) + f*g
    c = float64(float64(float64(a*b)-C)+float64(d*e)) + float64(f*g)

    Math Functions

    • Rounding: Use java.Round() instead of math.Round() to ensure consistent behavior for negative half-way values.
    • Integer Absolute Value: Use java.AbsInt() for integer values, as Go's math.Abs() only accepts float64.