ndslivemath

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 26, 2026 License: BSD-3-Clause Imports: 2 Imported by: 0

README

ndslive-math (Go)

Go port of ndslive-math, a small math library for NDS.Live geographic tiling. It provides WGS84 coordinate handling, Morton (Z-order) codes, packed tile IDs, and NDS-coordinate bounding boxes.

The C++ and Python implementations are the reference; this Go package mirrors their behaviour and is validated against the shared golden parity vectors in ../test-vectors/parity_vectors.json.

Installation

go get github.com/ndsev/ndslive-math/go
import ndslivemath "github.com/ndsev/ndslive-math/go"

Versioning / release tags (important)

Because this module lives in the go/ subdirectory of the repository (not at the repo root), Go's module tooling requires version tags to be prefixed with the module subdirectory path:

go/vX.Y.Z      ✅  correct — e.g. go/v0.5.2
vX.Y.Z         ❌  will NOT be picked up for this module

So to release v0.5.2 of the Go module, create and push the tag go/v0.5.2. Plain vX.Y.Z tags (used by other parts of the repo) are ignored by go get for this module.

Usage

package main

import (
	"fmt"

	ndslivemath "github.com/ndsev/ndslive-math/go"
)

func main() {
	// WGS84 -> NDS integer coordinates (uses floor, not truncation).
	berlin := ndslivemath.NewWgs84(13.404954, 52.520008, 0)
	x, y := berlin.ToNdsCoordinates()
	fmt.Println("NDS:", x, y) // 159927330 626588102

	// Round-trip back to degrees.
	back := ndslivemath.Wgs84FromNdsCoordinates(x, y)
	fmt.Printf("Lon=%.6f Lat=%.6f\n", back.Lon, back.Lat)

	// Morton codes.
	m := ndslivemath.MortonFromNdsCoordinates(x, y)
	fmt.Println("Morton:", m.Value())

	// Packed tile IDs. Level 15 tiles have negative signed values.
	tile, err := ndslivemath.PackedTileIdFromMortonAndLevel(m, 13)
	if err != nil {
		panic(err)
	}
	fmt.Println("Tile value:", tile.Value())
	fmt.Println("Level:", tile.Level(), "Morton#:", tile.MortonNumber())
	swX, swY := tile.SouthWestCorner()
	neX, neY := tile.NorthEastCorner() // NE corner is EXCLUSIVE
	fmt.Println("SW:", swX, swY, "NE(excl):", neX, neY)

	// Distance and bearing.
	munich := ndslivemath.NewWgs84(11.585, 48.137, 0)
	fmt.Printf("Distance: %.1f m\n", berlin.DistanceTo(munich))
	fmt.Printf("Bearing: %.6f rad\n", berlin.BearingFrom(munich))

	// Tiles covering a bounding box (NDS coords, floor division for negatives).
	tiles := ndslivemath.GetTileIdsForBoundingBox(0, 0, 268435456, 268435456, 3)
	fmt.Println("tiles:", len(tiles))

	// Bounding box ops.
	bbox := ndslivemath.NdsBoundingBoxFromTile(tile)
	_ = bbox
}

Semantic notes / correctness

The Go port carefully preserves behaviours where Go's semantics differ from Python's:

  • WGS84 → NDS uses math.Floor, not truncation; negative coordinates floor toward −∞.
  • PackedTileId.Value() is a signed int32 (negative for level 15); internally the value is stored unsigned (uint32).
  • MortonCode is a uint64; the encoder masks off bit 63.
  • NorthEastCorner is EXCLUSIVE, and BoundingBoxFromTileIds subtracts 1 from the maxima.
  • Floor division is used for bounding-box tile indexing (Go's / truncates toward zero for negatives; an explicit floorDiv helper is used instead).

Invalid tiles return an error (idiomatic Go) rather than panicking.

Testing

cd go
go test ./...

The tests load the shared golden vectors and assert every section, plus hand-written unit tests.

License

BSD-3-Clause — see LICENSE.

Documentation

Overview

Package ndslivemath provides NDS.Live mathematical utilities for geographic tiling: WGS84 coordinate handling, Morton (Z-order) codes, packed tile IDs and NDS-coordinate bounding boxes.

This is the Go port of the reference Python and C++ implementations. The public surface mirrors the Python package ndslive.math, adapted to idiomatic Go (errors instead of exceptions, multiple return values instead of tuples).

Index

Constants

View Source
const (
	// EarthRadiusInMeters is the approximate radius of Earth used for
	// great-circle (haversine) distance calculations.
	EarthRadiusInMeters = 6371000.8

	// MetersPerDegree is the approximate number of meters per degree of
	// latitude (and of longitude at the equator).
	MetersPerDegree = 111320.0
)

WGS84 / NDS conversion constants. These mirror the Python reference exactly.

Variables

View Source
var (
	// LonNdsDelta is 360 / (2^32 - 1).
	LonNdsDelta = 360.0 / float64((uint64(1)<<32)-1)
	// LatNdsDelta is 180 / (2^31 - 1).
	LatNdsDelta = 180.0 / float64((uint64(1)<<31)-1)
)

LonNdsDelta and LatNdsDelta are the smallest representable angular steps in the NDS coordinate system. They are defined as package-level variables (not const) because they involve floating-point division.

View Source
var (
	// LonNdsDeltaPow2 is 360 / 2^32 (== 8.381903171539307e-08).
	LonNdsDeltaPow2 = 360.0 / math.Exp2(32)
	// LatNdsDeltaPow2 is 180 / 2^31 (== 8.381903171539307e-08, numerically
	// equal to LonNdsDeltaPow2).
	LatNdsDeltaPow2 = 180.0 / math.Exp2(31)
	// LonMin is the minimum longitude (-180).
	LonMin = -180.0
	// LonMax is the maximum longitude (180 - LonNdsDeltaPow2 == 179.99999991618097).
	LonMax = 180.0 - LonNdsDeltaPow2
	// LatMin is the minimum latitude (-90).
	LatMin = -90.0
	// LatMax is the maximum latitude (90 - LatNdsDeltaPow2).
	LatMax = 90.0 - LatNdsDeltaPow2
)

Geometry-layer constants mirroring the C++ Wgs84<double> static members (cpp/include/ndsmath/wgs84.h). These intentionally use a power-of-two denominator (2^32 / 2^31) rather than LonNdsDelta / LatNdsDelta above (which use 2^32 - 1 / 2^31 - 1). The difference shows up in the 13th significant digit, but it is load-bearing for the geometry layer: Wgs84AABB antimeridian handling and tile-from-index construction must match the C++ reference bit-for-bit. Do NOT reuse LonNdsDelta / LatNdsDelta in the geometry layer.

Functions

func BoundingBoxFromTileIds

func BoundingBoxFromTileIds(tiles []PackedTileId) (int64, int64, int64, int64, error)

BoundingBoxFromTileIds computes the minimal NDS bounding box covering all the given tiles. It returns (minX, minY, maxX, maxY) where the NE corner has been decremented by 1 so that the box is inclusive (the NorthEastCorner of a tile is exclusive). It returns an error if tiles is empty.

Values are int64 because the intermediate (exclusive) maxima may exceed int32; the decremented results returned here are always within int32 range for valid tiles. A single-tile box round-trips: passing the result back through GetTileIdsForBoundingBox at the same level yields exactly that tile.

func DegreesToMeters

func DegreesToMeters(lonDegrees, latDegrees, atLatitude float64) (float64, float64)

DegreesToMeters converts degree distances to meters at a given latitude.

Longitude distance shrinks toward the poles (scaled by cos(latitude)); latitude distance is constant. Returns (widthMeters, heightMeters).

func NdsDistanceToMeters

func NdsDistanceToMeters(ndsXDistance, ndsYDistance, atLatitude float64) (float64, float64)

NdsDistanceToMeters converts NDS coordinate distances to meters at a given latitude. Returns (widthMeters, heightMeters).

Types

type MortonCode

type MortonCode struct {
	// contains filtered or unexported fields
}

MortonCode implements Morton encoding (Z-order curve) for 2D NDS coordinates. It wraps a 64-bit unsigned value and provides conversion to and from NDS integer coordinates.

func MortonFromNdsCoordinates

func MortonFromNdsCoordinates(xIn, yIn int32) MortonCode

MortonFromNdsCoordinates encodes NDS integer coordinates into a Morton (Z-order) code. It is the inverse of MortonCode.ToNdsCoordinates.

x is NDS longitude (signed 32-bit), y is NDS latitude (signed 31-bit); both are wrapped into range before encoding. Bit 63 is masked off, matching the Python/C++ reference (morton_code &= ~(1 << 63)).

The interleave loop is ported verbatim from morton.py. Note that the arithmetic is performed on signed 64-bit values (mirroring Python's arbitrary-precision ints and C++'s int64_t), then OR-ed into the unsigned accumulator. Using int64 here is essential: the X high bit is extracted at bit position 62 during the 32nd X step, and y is shifted left by 1 up front, so values can exceed the int32 range during the loop.

func NewMortonCode

func NewMortonCode(value uint64) MortonCode

NewMortonCode constructs a MortonCode from a raw 64-bit code value.

Most callers use MortonFromNdsCoordinates instead; this constructor is for round-tripping a previously computed code. The value is stored as-is (it is already 64-bit unsigned in Go, so no masking is needed for the full width).

func (MortonCode) ToNdsCoordinates

func (m MortonCode) ToNdsCoordinates() (int32, int32)

ToNdsCoordinates decodes this Morton code back into NDS integer coordinates. It is the inverse of MortonFromNdsCoordinates and returns (x, y) as signed NDS longitude (32-bit) and latitude (31-bit).

The deinterleave loop is ported verbatim from morton.py. The accumulators x and y are computed as int64 (the same width the encoder used) and then sign-adjusted against XBASE / YBASE before being narrowed to int32.

func (MortonCode) Value

func (m MortonCode) Value() uint64

Value returns the raw 64-bit Morton code value (matches the C++/Python API).

type NdsBoundingBox

type NdsBoundingBox struct {
	MinX int32 // SW corner longitude (NDS coords)
	MinY int32 // SW corner latitude (NDS coords)
	MaxX int32 // NE corner longitude (NDS coords)
	MaxY int32 // NE corner latitude (NDS coords)
}

NdsBoundingBox is an axis-aligned bounding box in NDS coordinates.

NDS coordinates use integers for fast comparisons:

  • X (longitude): 32-bit signed integer
  • Y (latitude): 31-bit signed integer

MinX/MinY are the SW corner; MaxX/MaxY are the NE corner.

func NdsBoundingBoxFromTile

func NdsBoundingBoxFromTile(tile PackedTileId) NdsBoundingBox

NdsBoundingBoxFromTile creates a bounding box covering the given tile's area.

The NE corner is the tile's exclusive corner (SW + size); for low-level tiles this can exceed the int32 range, so the corner is narrowed via reinterpreting conversion exactly as the C++ reference does with int32_t.

func NdsBoundingBoxFromWgs84Corners

func NdsBoundingBoxFromWgs84Corners(sw, ne Wgs84) NdsBoundingBox

NdsBoundingBoxFromWgs84Corners creates a bounding box from WGS84 corner coordinates (sw = min lon/lat, ne = max lon/lat), converting each corner to NDS coordinates.

func (NdsBoundingBox) Contains

func (b NdsBoundingBox) Contains(other NdsBoundingBox) bool

Contains reports whether other is completely inside this bounding box.

func (NdsBoundingBox) Intersects

func (b NdsBoundingBox) Intersects(other NdsBoundingBox) bool

Intersects reports whether this bounding box overlaps (shares any area with) other.

type Orientation

type Orientation int

Orientation is the winding order of a polygon. The integer values match the C++ enum (cpp/include/ndsmath/polygon.h) and are part of the parity output.

const (
	// Clockwise winding (negative signed area).
	Clockwise Orientation = -1
	// InvalidOrientation: not a simple polygon / single triangle, or zero area.
	InvalidOrientation Orientation = 0
	// CounterClockwise winding (positive signed area).
	CounterClockwise Orientation = 1
)

type PackedTileId

type PackedTileId struct {
	// contains filtered or unexported fields
}

PackedTileId represents a tile in the hierarchical NDS.Live tiling system.

Per the NDS.Live standard, tile IDs are signed 32-bit integers. For levels 0-14 the values are positive; for level 15 the values are negative (-2147483648 to -1) because the level bit (bit 31) coincides with the sign bit of a signed int32.

Internally the value is stored as an unsigned 32-bit integer so that all bit operations are free of sign-bit complications. Conversion between signed and unsigned happens only at the API boundary: constructors accept either representation, and Value returns the signed int32 mandated by the standard.

func GetTileIdsForBoundingBox

func GetTileIdsForBoundingBox(swX, swY, neX, neY int32, level int) []PackedTileId

GetTileIdsForBoundingBox returns all tile IDs that intersect the bounding box defined by the south-west and north-east corners in NDS coordinates, at the given level.

Floor division is used for the corner-to-tile-index mapping (matching Python's // operator), which matters for negative coordinates.

func NewPackedTileId

func NewPackedTileId(value int32) (PackedTileId, error)

NewPackedTileId constructs a PackedTileId from a signed int32 tile ID value.

Per the NDS.Live standard, level 15 tiles have negative values (-2147483648 to -1) while levels 0-14 have positive values. This accepts the signed representation directly; for unsigned input use NewPackedTileIdFromUnsigned.

It returns an error if the resulting tile ID is invalid.

func NewPackedTileIdFromUnsigned

func NewPackedTileIdFromUnsigned(value uint64) (PackedTileId, error)

NewPackedTileIdFromUnsigned constructs a PackedTileId from an unsigned tile ID value. Values outside the 32-bit range are masked to 32 bits, mirroring the Python constructor. It returns an error if the tile ID is invalid.

func PackedTileIdFromMortonAndLevel

func PackedTileIdFromMortonAndLevel(mortonCode MortonCode, level int) (PackedTileId, error)

PackedTileIdFromMortonAndLevel creates the PackedTileId of the tile at the given level that contains the full-precision NDS point encoded by mortonCode.

The resulting tile's MortonNumber will NOT generally equal mortonCode.Value(); use PackedTileIdFromTileIndex if you need a specific morton number. level must be in [0, 15].

func PackedTileIdFromTileIndex

func PackedTileIdFromTileIndex(mortonNumber uint32, level int) (PackedTileId, error)

PackedTileIdFromTileIndex creates a PackedTileId directly from a tile morton number and level, without any coordinate conversion.

mortonNumber must be in [0, 2^(2*level+1) - 1] and level in [0, 15]; otherwise an error is returned.

func (PackedTileId) Center

func (t PackedTileId) Center() (int64, int64)

Center returns the center of the tile in NDS coordinates.

Returned as int64 because, although the center always fits in int32 for valid tiles, the corner arithmetic it derives from can exceed int32.

func (PackedTileId) DimensionsInMeters

func (t PackedTileId) DimensionsInMeters() (float64, float64)

DimensionsInMeters returns the tile's (width, height) in meters, computed at the tile's center latitude. Width varies with cos(latitude); height is constant.

func (PackedTileId) EastNeighbour

func (t PackedTileId) EastNeighbour() PackedTileId

EastNeighbour returns the tile to the east at the same level, wrapping at the antimeridian.

func (PackedTileId) Level

func (t PackedTileId) Level() int

Level returns the level of the tile (0..15).

func (PackedTileId) MortonNumber

func (t PackedTileId) MortonNumber() uint32

MortonNumber returns the tile's Morton number (the value with the level-specific offset removed).

func (PackedTileId) NorthEastCorner

func (t PackedTileId) NorthEastCorner() (int64, int64)

NorthEastCorner returns the north-east (EXCLUSIVE) corner of the tile in NDS coordinates: the first coordinate outside the tile, i.e. SW + size.

Returned as int64 because for low levels (e.g. level 0) the exclusive corner is 2^31, which exceeds the signed int32 range.

func (PackedTileId) NorthNeighbour

func (t PackedTileId) NorthNeighbour() PackedTileId

NorthNeighbour returns the tile to the north at the same level, wrapping at the north pole.

func (PackedTileId) Size

func (t PackedTileId) Size() uint32

Size returns the tile's edge length in NDS coordinate units.

func (PackedTileId) SouthNeighbour

func (t PackedTileId) SouthNeighbour() PackedTileId

SouthNeighbour returns the tile to the south at the same level, wrapping at the south pole.

func (PackedTileId) SouthWestCorner

func (t PackedTileId) SouthWestCorner() (int64, int64)

SouthWestCorner returns the south-west (inclusive) corner of the tile in NDS coordinates. Returned as int64 for a uniform corner API; SW values always fit in int32.

func (PackedTileId) String

func (t PackedTileId) String() string

String implements fmt.Stringer.

func (PackedTileId) Value

func (t PackedTileId) Value() int32

Value returns the tile ID as a signed int32 per the NDS.Live standard. Level 15 tiles return negative values; levels 0-14 return positive values.

func (PackedTileId) WestNeighbour

func (t PackedTileId) WestNeighbour() PackedTileId

WestNeighbour returns the tile to the west at the same level, wrapping at the antimeridian.

type Polygon

type Polygon struct {
	// contains filtered or unexported fields
}

Polygon is a set of WGS84 vertices with a topology type and an orientation query. It mirrors the C++ Polygon<Vector> template, specialized directly to a slice of Wgs84 vertices. Orientation is computed on the raw (lon, lat) plane (no WGS84 normalization, no antimeridian handling).

Wgs84Polygon embeds Polygon and overrides validity semantics via its own IsValid method.

func NewPolygon

func NewPolygon(polygonType PolygonType) Polygon

NewPolygon constructs an empty polygon of the given type.

func NewPolygonWithVertices

func NewPolygonWithVertices(polygonType PolygonType, vertices []Wgs84) Polygon

NewPolygonWithVertices constructs a polygon of the given type with the supplied vertices (copied).

func (*Polygon) AddVertex

func (p *Polygon) AddVertex(position Wgs84)

AddVertex appends a single vertex.

func (*Polygon) AddVertices

func (p *Polygon) AddVertices(vertices []Wgs84)

AddVertices appends a list of vertices, in order.

func (Polygon) At

func (p Polygon) At(i int) Wgs84

At returns the vertex at index i (no bounds check beyond Go's own, mirroring the C++ operator[]).

func (Polygon) IsValid

func (p Polygon) IsValid() bool

IsValid reports whether this is a valid polygon. The base implementation requires at least one vertex; Wgs84Polygon overrides it to require >= 3.

func (Polygon) Len

func (p Polygon) Len() int

Len returns the number of vertices.

func (Polygon) Orientation

func (p Polygon) Orientation() Orientation

Orientation computes the winding order via the signed shoelace formula.

Only works for SimplePolygon and a single-triangle TriangleList (exactly 3 vertices). All other types return InvalidOrientation without computing area. Collinear vertices (zero area) also return InvalidOrientation. Uses raw (lon, lat) doubles; no normalization.

func (*Polygon) Set

func (p *Polygon) Set(i int, value Wgs84)

Set replaces the vertex at index i.

func (*Polygon) SetType

func (p *Polygon) SetType(polygonType PolygonType)

SetType sets the polygon type.

func (Polygon) Type

func (p Polygon) Type() PolygonType

Type returns the polygon type.

func (Polygon) Vertices

func (p Polygon) Vertices() []Wgs84

Vertices returns the underlying vertex slice.

type PolygonTriangulation

type PolygonTriangulation struct{}

PolygonTriangulation triangulates simple polygons by ear clipping (O(n^2)).

Port of the C++ PolygonTriangulation (cpp/include/ndsmath/ polygontriangulation.{h,cpp}). The C++ implementation uses a pointer-linked ring of partition vertices; this port uses an index-linked slice of partitionVertex (prev / next integer indices) to avoid manual memory management while reproducing the same traversal.

The set of output triangles is deterministic, but the ordering of triangles and the rotation of each triangle's three vertices are implementation- specific (driven by the "most extruded ear" tie-break and floating-point angle comparisons). Triangulation output is therefore NOT part of the cross-language parity vectors; tests assert structure (type and vertex count) only.

func (PolygonTriangulation) TriangulateByEarClipping

func (PolygonTriangulation) TriangulateByEarClipping(polygon Wgs84Polygon) Wgs84Polygon

TriangulateByEarClipping triangulates a CCW simple polygon into a TriangleList.

The input must be a SimplePolygon with at least 3 vertices, in counter-clockwise order. It returns a Wgs84Polygon of type TriangleList on success (3 * (n - 2) vertices), or a polygon of type PolygonUnknown on failure (wrong type, too few vertices, or no ear found).

type PolygonType

type PolygonType int

PolygonType is the topology of a polygon. The integer values match the C++ enum and are part of the parity output.

const (
	// SimplePolygon is a shape with an arbitrary number of vertices and no
	// holes; the last vertex connects back to the first.
	SimplePolygon PolygonType = 0
	// TriangleStrip is a triangle strip.
	TriangleStrip PolygonType = 1
	// TriangleFan is a triangle fan.
	TriangleFan PolygonType = 2
	// TriangleList is a set of independent triangles; three consecutive
	// vertices form one.
	TriangleList PolygonType = 3
	// PolygonUnknown is an illegal polygon type, used to signal failure.
	PolygonUnknown PolygonType = 4
)

type Vec2

type Vec2 struct {
	X float64
	Y float64
}

Vec2 is a plain, un-normalized 2D vector (X, Y).

The geometry layer (Wgs84AABB in particular) needs a raw (dx, dy) extent that can legitimately exceed 360 / 180 degrees or be negative — unlike Wgs84, whose constructor wraps longitude and clamps latitude. Reusing Wgs84 for a size/extent would silently corrupt those values via normalization, so this small struct is used instead.

It mirrors the role of glm::dvec2 (used as Wgs84<T>::vec2_t) in the C++ reference (cpp/include/ndsmath/wgs84aabb.h). X is the longitude/horizontal component, Y the latitude/vertical component.

func NewVec2

func NewVec2(x, y float64) Vec2

NewVec2 constructs a Vec2 from its X and Y components.

func (Vec2) Abs

func (v Vec2) Abs() Vec2

Abs returns the component-wise absolute value of this vector.

func (Vec2) Add

func (v Vec2) Add(other Vec2) Vec2

Add returns the component-wise sum of this vector and other.

func (Vec2) Scale

func (v Vec2) Scale(scalar float64) Vec2

Scale returns this vector with both components multiplied by scalar.

func (Vec2) Sub

func (v Vec2) Sub(other Vec2) Vec2

Sub returns the component-wise difference of this vector and other.

type Wgs84

type Wgs84 struct {
	Lon float64 // longitude in degrees, normalized into [-180, 180)
	Lat float64 // latitude in degrees, clamped into [-90, 90 - LatNdsDelta]
	Alt float64 // altitude in meters above the WGS84 ellipsoid
}

Wgs84 represents a point on the Earth's surface using the WGS84 coordinate system. Lon (X) and Lat (Y) are in degrees; Alt (Z) is meters above the WGS84 ellipsoid.

func NewWgs84

func NewWgs84(lon, lat, alt float64) Wgs84

NewWgs84 constructs a WGS84 point in degrees and normalizes it: longitude is wrapped into [-180, 180) and latitude is clamped into [-90, 90 - LatNdsDelta].

func Wgs84FromMortonCode

func Wgs84FromMortonCode(mortonCode MortonCode) Wgs84

Wgs84FromMortonCode constructs a Wgs84 point (with Alt=0) from a Morton code.

This mirrors the C++ Wgs84<double>::fromMortonCode (cpp/include/ndsmath/wgs84.h). Note that it scales BOTH the NDS x (longitude) and the NDS y (latitude) by the same factor 360 / 2^32 — unlike Wgs84FromNdsCoordinates, which scales latitude by 180 / 2^31. This difference is intentional in the reference and is relied upon by the geometry layer (e.g. the Wgs84AABB tile-from-index constructor); do not "fix" it.

func Wgs84FromNdsCoordinates

func Wgs84FromNdsCoordinates(x, y int32) Wgs84

Wgs84FromNdsCoordinates constructs a Wgs84 point (with Alt=0) from NDS integer coordinates. It is the inverse of ToNdsCoordinates.

func (Wgs84) BearingFrom

func (w Wgs84) BearingFrom(other Wgs84) float64

BearingFrom returns the initial bearing (forward azimuth) in radians from other toward this point, measured clockwise from true north.

func (Wgs84) DistanceTo

func (w Wgs84) DistanceTo(other Wgs84) float64

DistanceTo returns the great-circle distance in meters to another point, computed via the haversine formula using EarthRadiusInMeters.

func (Wgs84) Dx

func (w Wgs84) Dx() float64

Dx returns the X (longitude) component. Mirrors the C++ dx() accessor.

func (Wgs84) Dy

func (w Wgs84) Dy() float64

Dy returns the Y (latitude) component. Mirrors the C++ dy() accessor.

func (Wgs84) Equals

func (w Wgs84) Equals(other Wgs84) bool

Equals reports whether two points have approximately equal longitude and latitude (relative tolerance 1e-12), mirroring Python's math.isclose.

func (Wgs84) Latitude

func (w Wgs84) Latitude() float64

Latitude returns the latitude (Y) in degrees. Mirrors the C++ accessor.

func (Wgs84) Longitude

func (w Wgs84) Longitude() float64

Longitude returns the longitude (X) in degrees. Mirrors the C++ accessor.

func (*Wgs84) Normalize

func (w *Wgs84) Normalize()

Normalize wraps longitude into [-180, 180) and clamps latitude into [-90, 90 - LatNdsDelta]. It is called automatically by NewWgs84; call it explicitly after mutating Lon or Lat directly.

This mirrors the Python Wgs84.normalize() logic precisely, including the math.Mod (fmod) sign-preserving wrap and the boundary snapping for values very close to the positive limit.

func (Wgs84) Sub

func (w Wgs84) Sub(other Wgs84) Wgs84

Sub returns the component-wise difference of this point and other, then re-normalizes the result (longitude wrap, latitude clamp). It mirrors the re-normalizing Wgs84 operator- of the C++/Python reference, which the ear-clipping triangulation relies on.

func (Wgs84) ToDegreeMinutesSeconds

func (w Wgs84) ToDegreeMinutesSeconds() (string, string)

ToDegreeMinutesSeconds formats this point as degrees-minutes-seconds strings. It returns (latStr, lonStr) in the form "DD° MM' SS.ss\" N|S" and "DDD° MM' SS.ss\" E|W".

func (Wgs84) ToNdsCoordinates

func (w Wgs84) ToNdsCoordinates() (int32, int32)

ToNdsCoordinates converts this WGS84 point to NDS integer coordinates.

NDS allows floor, truncate, or round for this conversion. Floor is used here (as recommended by NDS and matching the reference implementations). This is significant for negative coordinates, which floor toward negative infinity rather than truncating toward zero.

type Wgs84AABB

type Wgs84AABB struct {
	// contains filtered or unexported fields
}

Wgs84AABB is a WGS84 axis-aligned bounding box.

Port of the C++ Wgs84AABB<T> (cpp/include/ndsmath/wgs84aabb.h), specialized to float64. The box is stored as a south-west corner (a Wgs84) plus a raw Vec2 size (a (dx, dy) extent that is NOT normalized).

The geometry layer deliberately uses the power-of-two NDS deltas (LonNdsDeltaPow2 etc.) so antimeridian handling and tile-from-index construction match the C++ reference bit-for-bit.

func NewWgs84AABB

func NewWgs84AABB(sw Wgs84, size Vec2) Wgs84AABB

NewWgs84AABB constructs an AABB from a south-west corner and a size.

After storing, if the box is valid, the height is clamped so the top never exceeds +90 degrees (the excessHeight correction). An invalid box stores size unmodified.

func Wgs84AABBFromCenterAndTileLimit

func Wgs84AABBFromCenterAndTileLimit(center Wgs84, softLimit uint32, level int) Wgs84AABB

Wgs84AABBFromCenterAndTileLimit constructs an AABB from a center, a soft tile-count limit, and a level.

func Wgs84AABBFromTile

func Wgs84AABBFromTile(tileID PackedTileId) Wgs84AABB

Wgs84AABBFromTile constructs an AABB covering a tile (Path A: via Wgs84FromMortonCode).

Mirrors the C++ Wgs84AABB(PackedTileId) constructor: the SW/NE NDS corners are wrapped into a MortonCode and converted via Wgs84FromMortonCode, so both axes are scaled by 360 / 2^32 (NOT Wgs84FromNdsCoordinates, which scales latitude by 180 / 2^31 and would diverge from C++).

func (Wgs84AABB) AvgMercatorStretch

func (b Wgs84AABB) AvgMercatorStretch() float64

AvgMercatorStretch returns the Mercator-projection vertical stretch factor.

Transcendental; not part of the cross-language parity vectors (asserted only for finiteness in unit tests).

func (Wgs84AABB) Center

func (b Wgs84AABB) Center() Wgs84

Center returns the center coordinate (sw + size * 0.5, re-normalized).

func (Wgs84AABB) Contains

func (b Wgs84AABB) Contains(point Wgs84) bool

Contains reports whether point lies within the box (inclusive on all edges).

func (Wgs84AABB) ContainsAntiMeridian

func (b Wgs84AABB) ContainsAntiMeridian() bool

ContainsAntiMeridian reports whether the horizontal extent crosses the anti-meridian (+/-180).

func (Wgs84AABB) Intersects

func (b Wgs84AABB) Intersects(other Wgs84AABB) bool

Intersects is the axis-aligned interval-overlap test against another box.

This is the fixed test: a pure interval overlap on longitude and latitude. Edge-touching counts as intersecting; it correctly detects cross-shaped overlaps and never recurses on disjoint boxes.

func (Wgs84AABB) NE

func (b Wgs84AABB) NE() Wgs84

NE returns the north-east corner (sw + size, re-normalized).

func (Wgs84AABB) NW

func (b Wgs84AABB) NW() Wgs84

NW returns the north-west corner.

func (Wgs84AABB) NumTileIds

func (b Wgs84AABB) NumTileIds(lv int) int

NumTileIds returns the approximate number of tiles at level lv contained in this box.

Mirrors the C++ numTileIds: tileWidth = 180 / float32(2^lv) and a component-wise ceil of size / tileWidth. The C++ code casts 1u<<lv to a 32-bit float; for lv <= 31 that cast is exact for powers of two. The product is truncated toward zero to an integer (matching the reference int() cast), so it can be negative for invalid (negative-size) boxes; that is preserved.

func (Wgs84AABB) SE

func (b Wgs84AABB) SE() Wgs84

SE returns the south-east corner.

func (Wgs84AABB) SW

func (b Wgs84AABB) SW() Wgs84

SW returns the south-west corner.

func (Wgs84AABB) Size

func (b Wgs84AABB) Size() Vec2

Size returns the raw (dx, dy) size of the box.

func (Wgs84AABB) SplitOverAntiMeridian

func (b Wgs84AABB) SplitOverAntiMeridian() (left, right Wgs84AABB, ok bool)

SplitOverAntiMeridian splits a box crossing the anti-meridian into a left and a right half.

Only meaningful when ContainsAntiMeridian is true. Returns the two normalized boxes and ok=true, or two zero boxes and ok=false if the box does not actually extend past LonMax.

func (Wgs84AABB) TileLevel

func (b Wgs84AABB) TileLevel(minNumTiles int) int

TileLevel returns the first level (0..15) whose tile count is at least minNumTiles. Returns 15 if no level in 0..15 reaches the threshold.

func (Wgs84AABB) Valid

func (b Wgs84AABB) Valid() bool

Valid reports whether the box size is within reasonable bounds: 0 <= size.X <= 360 and 0 <= size.Y <= 180.

func (Wgs84AABB) Vertices

func (b Wgs84AABB) Vertices() []Wgs84

Vertices returns all four corners, CCW from SW: [sw, se, ne, nw].

type Wgs84Polygon

type Wgs84Polygon struct {
	Polygon
}

Wgs84Polygon is a simple polygon of WGS84 vertices with bounding box, median, and Separating-Axis-Theorem collision helpers.

Port of the C++ HighPrecWgs84Polygon (cpp/include/ndsmath/wgs84polygon.h). It embeds Polygon, defaults to SimplePolygon, and overrides validity to require at least three vertices. The collision math runs on raw (lon, lat) doubles: no normalization, no antimeridian handling.

func EarthWrappingPoly

func EarthWrappingPoly() Wgs84Polygon

EarthWrappingPoly returns the 4-vertex sentinel polygon wrapping the whole Earth.

Constructed from (-180,-90), (-180,90), (180,-90), (180,90). These coordinates pass through Wgs84 normalization, so the stored values are not exactly those literals. This polygon is only used as an identity sentinel in CollidesWith via Equals; its exact normalized coordinates are not part of cross-language parity.

func NewWgs84Polygon

func NewWgs84Polygon() Wgs84Polygon

NewWgs84Polygon constructs an empty simple WGS84 polygon.

func NewWgs84PolygonOfType

func NewWgs84PolygonOfType(polygonType PolygonType) Wgs84Polygon

NewWgs84PolygonOfType constructs an empty WGS84 polygon of the given type.

func NewWgs84PolygonOfTypeWithVertices

func NewWgs84PolygonOfTypeWithVertices(polygonType PolygonType, vertices []Wgs84) Wgs84Polygon

NewWgs84PolygonOfTypeWithVertices constructs a WGS84 polygon of the given type with the given vertices.

func NewWgs84PolygonWithVertices

func NewWgs84PolygonWithVertices(vertices []Wgs84) Wgs84Polygon

NewWgs84PolygonWithVertices constructs a simple WGS84 polygon with the given vertices.

func (Wgs84Polygon) AaBb

func (p Wgs84Polygon) AaBb() Wgs84AABB

AaBb returns the axis-aligned bounding box of this polygon.

Returns a default (empty) Wgs84AABB if the polygon is invalid (fewer than 3 vertices). The size is computed as raw coordinate differences (not normalized), then handed to the Wgs84AABB constructor, which still applies the excess-height clamp.

func (Wgs84Polygon) CollidesWith

func (p Wgs84Polygon) CollidesWith(other Wgs84Polygon) bool

CollidesWith reports whether this polygon collides with other (via the Separating-Axis Theorem).

The earth-wrapping sentinel collides with everything. Otherwise the SAT axis sets are taken from this polygon's edges (first test) and from other's edges (second test); if no separating axis is found on either, the polygons collide.

func (Wgs84Polygon) Equals

func (p Wgs84Polygon) Equals(other Wgs84Polygon) bool

Equals reports order-sensitive vertex-wise equality via Wgs84 approximate equality.

func (Wgs84Polygon) IsValid

func (p Wgs84Polygon) IsValid() bool

IsValid reports whether this is a valid polygon: at least three vertices. It overrides the embedded Polygon.IsValid (which requires only one vertex).

func (Wgs84Polygon) Median

func (p Wgs84Polygon) Median() Wgs84

Median returns the centroid (mean longitude, mean latitude) of the polygon vertices. The means are accumulated as sum(coord / n) (not sum(coord) / n) to match the C++ reference's floating-point rounding.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL