Skip to content

mortie.geometry

Lazy WKB/WKT geometry codec. The geometry backend (shapely>=2 preferred, spherely accepted) is imported on first use, so numpy stays the only runtime dependency.

from_wkb needs no backend at all (issue #157): mortie parses WKB itself, in Rust, and covers the rings directly. A backend is still required for WKT ingest (there is no Rust WKT parser) and for the whole emit direction, which hands back a backend geometry object by definition. The batch form, from_wkbs, lives in mortie.batch (issue #170).

The spherical outline machinery behind to_geometry(dissolve=True) lives in mortie.dissolve (issue #159), mirroring src_rust/src/dissolve.rs, and the backend gate plus the codec quartet live in mortie.codec. Neither has any public member, so neither has a page of its own; the functions below are still where the whole ingest/emit path is documented.

WKB/WKT geometry ingest and emit for mortie (issue #71).

The runtime stays numpy-only: :mod:mortie.codec imports a geometry backend (shapely>=2 preferred, spherely accepted) lazily, and this module uses it only as a codec — bytes/text ↔ ring coordinate arrays. All spherical correctness (antimeridian / pole handling) stays mortie's own job; the backend is never asked for spatial predicates. Importing :mod:mortie succeeds with neither backend installed; the geometry functions raise a clear :class:ImportError when first touched without one (the same lazy-gate pattern :mod:mortie.arrow uses for pyarrow).

WKB ingest needs no backend at all (issue #157): :func:from_wkb parses the bytes with mortie's own Rust reader and feeds the rings straight to the coverage kernels. What still needs a backend is WKT ingest (there is no Rust WKT parser) and the whole emit direction — :func:to_geometry and friends hand back a geometry object, which is a backend object by definition.

Coordinate convention: WKB/WKT store (x, y) = (lon, lat) degrees (EPSG:4326). mortie's coverage entry points take (lats, lons), so this module flips the axes at the boundary and works in degrees throughout.

from_wkb(data, order=18, moc=False, normalize=True, tolerance=None, max_cells=None)

Cover a geometry given as WKB (or EWKB) bytes -- no backend needed.

The blob is parsed by mortie's own Rust WKB reader (issue #157) and its rings go straight to the coverage kernels, so this works with neither shapely nor spherely installed — mortie's runtime really is numpy-only on this path. The cover is identical to what the backend-decoded path produced: same rings, same descent. (:func:from_wkt still decodes via a backend — #157 scoped the Rust parser to WKB.)

Parameters:

Name Type Description Default
data bytes, str, or buffer

WKB or EWKB bytes. Both byte orders, the ISO and EWKB dimension spellings (Z/M are dropped — mortie is 2-D lon/lat), and an EWKB SRID prefix (stripped; mortie's contract is always EPSG:4326) are accepted. A hex string of the blob is accepted too, as the backend-decoded path accepted one; so is any byte buffer (bytearray / memoryview / a uint8 array), which the backend path did not — a deliberate widening for arrow-backed callers. Anything else (an iterable of ints included) is a TypeError naming its type.

required
order optional

Forwarded to :func:from_geometry unchanged. See there for the full contract — in particular that morton_coverage_moc has no orientation auto-correct, so with moc=True the ring winding is taken as authored.

18
moc optional

Forwarded to :func:from_geometry unchanged. See there for the full contract — in particular that morton_coverage_moc has no orientation auto-correct, so with moc=True the ring winding is taken as authored.

18
normalize optional

Forwarded to :func:from_geometry unchanged. See there for the full contract — in particular that morton_coverage_moc has no orientation auto-correct, so with moc=True the ring winding is taken as authored.

18
tolerance optional

Forwarded to :func:from_geometry unchanged. See there for the full contract — in particular that morton_coverage_moc has no orientation auto-correct, so with moc=True the ring winding is taken as authored.

18
max_cells optional

Forwarded to :func:from_geometry unchanged. See there for the full contract — in particular that morton_coverage_moc has no orientation auto-correct, so with moc=True the ring winding is taken as authored.

18

Returns:

Type Description
numpy.ndarray or list of numpy.ndarray

As :func:from_geometry.

Raises:

Type Description
ValueError

As :func:from_geometry — including moc / tolerance / max_cells passed for linear geometry — plus, from the reader, a truncated or malformed blob (an unclosed polygon ring included), an unsupported geometry type, or an empty geometry; and for a str that is not valid hex.

TypeError

For an input that is neither a string nor a buffer of bytes.

See Also

from_geometry : The shared parameter semantics and the full contract. mortie.batch.from_wkbs : the batch form (many blobs in one call).

Source code in mortie/geometry.py
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
def from_wkb(data, order=18, moc=False, normalize=True,
             tolerance=None, max_cells=None):
    """Cover a geometry given as WKB (or EWKB) bytes -- **no backend needed**.

    The blob is parsed by mortie's own Rust WKB reader (issue #157) and its
    rings go straight to the coverage kernels, so this works with neither
    shapely nor spherely installed — mortie's runtime really is numpy-only on
    this path.  The cover is identical to what the backend-decoded path
    produced: same rings, same descent.  (:func:`from_wkt` still decodes via a
    backend — #157 scoped the Rust parser to WKB.)

    Parameters
    ----------
    data : bytes, str, or buffer
        WKB or EWKB bytes.  Both byte orders, the ISO and EWKB dimension
        spellings (Z/M are dropped — mortie is 2-D lon/lat), and an EWKB SRID
        prefix (stripped; mortie's contract is always EPSG:4326) are accepted.
        A **hex string** of the blob is accepted too, as the backend-decoded
        path accepted one; so is any **byte buffer** (``bytearray`` /
        ``memoryview`` / a ``uint8`` array), which the backend path did not —
        a deliberate widening for arrow-backed callers.  Anything else (an
        iterable of ints included) is a ``TypeError`` naming its type.
    order, moc, normalize, tolerance, max_cells : optional
        Forwarded to :func:`from_geometry` unchanged.  See there for the full
        contract — in particular that ``morton_coverage_moc`` has no
        orientation auto-correct, so with ``moc=True`` the ring winding is
        taken **as authored**.

    Returns
    -------
    numpy.ndarray or list of numpy.ndarray
        As :func:`from_geometry`.

    Raises
    ------
    ValueError
        As :func:`from_geometry` — including ``moc`` / ``tolerance`` /
        ``max_cells`` passed for linear geometry — plus, from the reader, a
        truncated or malformed blob (an unclosed polygon ring included), an
        unsupported geometry type, or an empty geometry; and for a ``str``
        that is not valid hex.
    TypeError
        For an input that is neither a string nor a buffer of bytes.

    See Also
    --------
    from_geometry : The shared parameter semantics and the full contract.
    mortie.batch.from_wkbs : the batch form (many blobs in one call).
    """
    kind, parts = _rings_from_wkb(data)
    return _cover_parts(kind, parts, order, moc, normalize, tolerance, max_cells)

from_wkt(text, order=18, moc=False, normalize=True, tolerance=None, max_cells=None)

Cover a geometry given as WKT (or EWKT) text.

Thin wrapper: decode with the geometry backend, then :func:from_geometry. Unlike :func:from_wkb, this does need a backend installed — mortie has no Rust WKT parser (issue #157 scoped the reader to WKB).

Parameters:

Name Type Description Default
text str

WKT or EWKT text.

required
order optional

Forwarded to :func:from_geometry unchanged. See there for the full contract — in particular that morton_coverage_moc has no orientation auto-correct, so with moc=True the ring winding is taken as authored.

18
moc optional

Forwarded to :func:from_geometry unchanged. See there for the full contract — in particular that morton_coverage_moc has no orientation auto-correct, so with moc=True the ring winding is taken as authored.

18
normalize optional

Forwarded to :func:from_geometry unchanged. See there for the full contract — in particular that morton_coverage_moc has no orientation auto-correct, so with moc=True the ring winding is taken as authored.

18
tolerance optional

Forwarded to :func:from_geometry unchanged. See there for the full contract — in particular that morton_coverage_moc has no orientation auto-correct, so with moc=True the ring winding is taken as authored.

18
max_cells optional

Forwarded to :func:from_geometry unchanged. See there for the full contract — in particular that morton_coverage_moc has no orientation auto-correct, so with moc=True the ring winding is taken as authored.

18

Returns:

Type Description
numpy.ndarray or list of numpy.ndarray

As :func:from_geometry.

Raises:

Type Description
ValueError

As :func:from_geometry — including moc / tolerance / max_cells passed for linear geometry.

See Also

from_geometry : The shared parameter semantics and the full contract.

Source code in mortie/geometry.py
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
def from_wkt(text, order=18, moc=False, normalize=True,
             tolerance=None, max_cells=None):
    """Cover a geometry given as WKT (or EWKT) text.

    Thin wrapper: decode with the geometry backend, then
    :func:`from_geometry`.  Unlike :func:`from_wkb`, this **does** need a
    backend installed — mortie has no Rust WKT parser (issue #157 scoped the
    reader to WKB).

    Parameters
    ----------
    text : str
        WKT or EWKT text.
    order, moc, normalize, tolerance, max_cells : optional
        Forwarded to :func:`from_geometry` unchanged.  See there for the full
        contract — in particular that ``morton_coverage_moc`` has no
        orientation auto-correct, so with ``moc=True`` the ring winding is
        taken **as authored**.

    Returns
    -------
    numpy.ndarray or list of numpy.ndarray
        As :func:`from_geometry`.

    Raises
    ------
    ValueError
        As :func:`from_geometry` — including ``moc`` / ``tolerance`` /
        ``max_cells`` passed for linear geometry.

    See Also
    --------
    from_geometry : The shared parameter semantics and the full contract.
    """
    return from_geometry(
        _geometry_from_wkt(text), order=order, moc=moc, normalize=normalize,
        tolerance=tolerance, max_cells=max_cells,
    )

from_geometry(geom, order=18, moc=False, normalize=True, tolerance=None, max_cells=None)

Cover a backend geometry with morton indices (issue #71).

The geometry is decomposed via :func:decompose and routed to mortie's existing coverage entry points — so WKB/WKT ingest produces exactly the same cover as calling those functions on the same (lats, lons) arrays.

  • Polygon / MultiPolygon → :func:mortie.morton_coverage (flat) or, with moc=True, :func:mortie.morton_coverage_moc (compact mixed-order). Holes and disjoint parts are handled by the one even-odd descent.
  • LineString / MultiLineString → :func:mortie.linestring_coverage.

Parameters:

Name Type Description Default
geom backend geometry

A shapely/spherely geometry object (e.g. from shapely.from_wkb).

required
order int

HEALPix order (1–29). Default 18.

18
moc bool

Polygonal only: return a compact MOC instead of a flat cover.

False
normalize bool

Polygonal: auto-correct ring orientation at ingest, on both the flat and the moc=True path (see :func:mortie.morton_coverage). Default True: any simple ring whose interior decisively reads as the larger region is reversed so the smaller side is covered (S2's convention; issue #144 decision (A)), hemisphere-plus rings included. Pass False to take the winding as authored — the only way a WKB/WKT ring can express a bigger-than-complement interior (wind every ring, holes included, with its intended region on the left). normalize=False with linear geometry raises ValueError (a line has no ring orientation).

True
tolerance optional

Polygonal moc=True only: the adaptive stop criteria of :func:mortie.morton_coverage_moc (mutually exclusive).

None
max_cells optional

Polygonal moc=True only: the adaptive stop criteria of :func:mortie.morton_coverage_moc (mutually exclusive).

None

Returns:

Type Description
numpy.ndarray or list of numpy.ndarray

Polygonal → 1-D uint64 morton array. LineString → 1-D array; MultiLineString → list of arrays, one per line (the :func:mortie.linestring_coverage contract).

Raises:

Type Description
ValueError

If moc / tolerance / max_cells are passed for linear geometry (they apply only to polygonal geometry), or from :func:decompose for an unsupported or empty geometry.

Source code in mortie/geometry.py
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
def from_geometry(geom, order=18, moc=False, normalize=True,
                  tolerance=None, max_cells=None):
    """Cover a backend geometry with morton indices (issue #71).

    The geometry is decomposed via :func:`decompose` and routed to mortie's
    existing coverage entry points — so WKB/WKT ingest produces exactly the same
    cover as calling those functions on the same ``(lats, lons)`` arrays.

    * **Polygon / MultiPolygon** → :func:`mortie.morton_coverage` (flat) or, with
      ``moc=True``, :func:`mortie.morton_coverage_moc` (compact mixed-order).
      Holes and disjoint parts are handled by the one even-odd descent.
    * **LineString / MultiLineString** → :func:`mortie.linestring_coverage`.

    Parameters
    ----------
    geom : backend geometry
        A shapely/spherely geometry object (e.g. from ``shapely.from_wkb``).
    order : int, optional
        HEALPix order (1–29).  Default 18.
    moc : bool, optional
        Polygonal only: return a compact MOC instead of a flat cover.
    normalize : bool, optional
        Polygonal: auto-correct ring orientation at ingest, on both the
        flat and the ``moc=True`` path (see :func:`mortie.morton_coverage`).
        Default ``True``: any simple ring whose interior decisively reads as
        the larger region is reversed so the smaller side is covered (S2's
        convention; issue #144 decision (A)), hemisphere-plus rings included.
        Pass ``False`` to take the winding **as authored** — the only way a
        WKB/WKT ring can express a bigger-than-complement interior (wind
        every ring, holes included, with its intended region on the left).
        ``normalize=False`` with linear geometry raises ``ValueError`` (a
        line has no ring orientation).
    tolerance, max_cells : optional
        Polygonal ``moc=True`` only: the adaptive stop criteria of
        :func:`mortie.morton_coverage_moc` (mutually exclusive).

    Returns
    -------
    numpy.ndarray or list of numpy.ndarray
        Polygonal → 1-D ``uint64`` morton array.  LineString → 1-D array;
        MultiLineString → list of arrays, one per line (the
        :func:`mortie.linestring_coverage` contract).

    Raises
    ------
    ValueError
        If ``moc`` / ``tolerance`` / ``max_cells`` are passed for linear
        geometry (they apply only to polygonal geometry), or from
        :func:`decompose` for an unsupported or empty geometry.
    """
    kind, parts = decompose(geom)
    return _cover_parts(kind, parts, order, moc, normalize, tolerance, max_cells)

to_wkb(morton, dissolve=True, step=1, srid=None)

Emit a morton cover as WKB (or EWKB) bytes.

Parameters:

Name Type Description Default
morton array_like of uint64

A morton cover (flat or mixed-order MOC).

required
dissolve optional

Forwarded to :func:to_geometry unchanged; see there for the full contract (pole caps, antimeridian splitting, edge densification).

True
step optional

Forwarded to :func:to_geometry unchanged; see there for the full contract (pole caps, antimeridian splitting, edge densification).

True
srid int

With srid set (e.g. 4326), emit EWKB carrying that SRID; otherwise plain WKB.

None

Returns:

Type Description
bytes

The encoded WKB (or EWKB) bytes.

Raises:

Type Description
NotImplementedError

As :func:to_geometry — a non-shapely backend, or a dissolved hole that nests into no exterior.

See Also

to_geometry : The dissolve / step contract in full.

Source code in mortie/geometry.py
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
def to_wkb(morton, dissolve=True, step=1, srid=None):
    """Emit a morton cover as WKB (or EWKB) bytes.

    Parameters
    ----------
    morton : array_like of uint64
        A morton cover (flat or mixed-order MOC).
    dissolve, step : optional
        Forwarded to :func:`to_geometry` unchanged; see there for the full
        contract (pole caps, antimeridian splitting, edge densification).
    srid : int, optional
        With ``srid`` set (e.g. ``4326``), emit EWKB carrying that SRID;
        otherwise plain WKB.

    Returns
    -------
    bytes
        The encoded WKB (or EWKB) bytes.

    Raises
    ------
    NotImplementedError
        As :func:`to_geometry` — a non-shapely backend, or a dissolved hole
        that nests into no exterior.

    See Also
    --------
    to_geometry : The ``dissolve`` / ``step`` contract in full.
    """
    geom = to_geometry(morton, dissolve=dissolve, step=step)
    return _geometry_to_wkb(geom, srid=srid)

to_wkt(morton, dissolve=True, step=1, srid=None)

Emit a morton cover as WKT (or EWKT) text.

Parameters:

Name Type Description Default
morton array_like of uint64

A morton cover (flat or mixed-order MOC).

required
dissolve optional

Forwarded to :func:to_geometry unchanged; see there for the full contract (pole caps, antimeridian splitting, edge densification).

True
step optional

Forwarded to :func:to_geometry unchanged; see there for the full contract (pole caps, antimeridian splitting, edge densification).

True
srid int

With srid set, emit EWKT (SRID=<n>;<WKT>); otherwise plain WKT.

None

Returns:

Type Description
str

The encoded WKT (or EWKT) text.

Raises:

Type Description
NotImplementedError

As :func:to_geometry — a non-shapely backend, or a dissolved hole that nests into no exterior.

See Also

to_geometry : The dissolve / step contract in full.

Source code in mortie/geometry.py
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
def to_wkt(morton, dissolve=True, step=1, srid=None):
    """Emit a morton cover as WKT (or EWKT) text.

    Parameters
    ----------
    morton : array_like of uint64
        A morton cover (flat or mixed-order MOC).
    dissolve, step : optional
        Forwarded to :func:`to_geometry` unchanged; see there for the full
        contract (pole caps, antimeridian splitting, edge densification).
    srid : int, optional
        With ``srid`` set, emit EWKT (``SRID=<n>;<WKT>``); otherwise plain WKT.

    Returns
    -------
    str
        The encoded WKT (or EWKT) text.

    Raises
    ------
    NotImplementedError
        As :func:`to_geometry` — a non-shapely backend, or a dissolved hole
        that nests into no exterior.

    See Also
    --------
    to_geometry : The ``dissolve`` / ``step`` contract in full.
    """
    geom = to_geometry(morton, dissolve=dissolve, step=step)
    return _geometry_to_wkt(geom, srid=srid)

to_geometry(morton, dissolve=True, step=1)

Convert a morton cover to a backend geometry (issue #71).

Parameters:

Name Type Description Default
morton array_like of uint64

A morton cover (flat or mixed-order MOC; each word self-encodes order).

required
dissolve bool

True (default) emits the single dissolved outline of the whole cover (exterior rings, holes, and disjoint components), built natively by edge-cancellation — no backend spatial predicate. False emits a per-cell MultiPolygon — one quad per cell.

True
step int

Boundary points per cell edge (default 1 = 4 corners / straight chords). step>1 densifies each edge to follow the curved HEALPix boundary.

1

Returns:

Type Description
backend geometry

A shapely (or spherely) MultiPolygon in EPSG:4326 lon/lat degrees.

Raises:

Type Description
NotImplementedError

If the active backend is not shapely, or if a dissolved hole nests into no exterior (pass dissolve=False).

Notes

Emit requires the shapely backend (it constructs geometry objects). The dissolved emit (dissolve=True) handles pole-enclosing covers (e.g. polar caps), exteriors crossing the antimeridian any even number of times, and antimeridian-crossing holes: crossing rings are cut at ±180° and reconnected by the GeoJSON convention — a single split MultiPolygon with explicit ±90° pole vertices stitched down the antimeridian. A cover spanning near or over a hemisphere (2π sr), or one with a boundary ring enclosing more than a hemisphere (e.g. an equatorial band), raises ValueError — its exterior/hole winding is ambiguous (issue #108); split such a cover or use dissolve=False.

Source code in mortie/geometry.py
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
def to_geometry(morton, dissolve=True, step=1):
    """Convert a morton cover to a backend geometry (issue #71).

    Parameters
    ----------
    morton : array_like of uint64
        A morton cover (flat or mixed-order MOC; each word self-encodes order).
    dissolve : bool, optional
        ``True`` (default) emits the single dissolved outline of the whole cover
        (exterior rings, holes, and disjoint components), built natively by
        edge-cancellation — no backend spatial predicate.  ``False`` emits a
        per-cell ``MultiPolygon`` — one quad per cell.
    step : int, optional
        Boundary points per cell edge (default 1 = 4 corners / straight chords).
        ``step>1`` densifies each edge to follow the curved HEALPix boundary.

    Returns
    -------
    backend geometry
        A shapely (or spherely) ``MultiPolygon`` in EPSG:4326 lon/lat degrees.

    Raises
    ------
    NotImplementedError
        If the active backend is not shapely, or if a dissolved hole nests
        into no exterior (pass ``dissolve=False``).

    Notes
    -----
    Emit requires the shapely backend (it constructs geometry objects).  The
    dissolved emit (``dissolve=True``) handles pole-enclosing covers (e.g. polar
    caps), exteriors crossing the antimeridian any even number of times, and
    antimeridian-crossing holes: crossing rings are cut at ±180° and reconnected
    by the GeoJSON convention — a single split ``MultiPolygon`` with explicit
    ±90° pole vertices stitched down the antimeridian.  A cover spanning near
    or over a hemisphere (2π sr), or one with a boundary ring enclosing more
    than a hemisphere (e.g. an equatorial band), raises ``ValueError`` — its
    exterior/hole winding is ambiguous (issue #108); split such a cover or use
    ``dissolve=False``.
    """
    mod = _require_shapely("geometry emit")
    if dissolve:
        return mod.MultiPolygon(_dissolved_polygons(mod, morton, step))
    return mod.MultiPolygon(_per_cell_polygons(mod, morton, step))