mortie.Moc — the coverage object
mortie.moc(...) builds a Moc: a multi-order coverage as an object, so that
coverage geometry reads as geometry.
from mortie import moc
cali = moc(cali_geojson) # multi-order coverage; no order argument
q = moc(aoi_geojson)
assert cali.contains(q)
q9 = q.to_order(9) # fixed-order cast when a consumer's grid wants one
The two-layer rule
mortie's coverage surface is two layers and stays that way:
- The kernel functions are the array/batch layer. The free
moc_*functions on mortie MOC kernel are words in, words out, unchanged and un-deprecated, and the plural forms in mortie.batch (mocs_and,mocs_intersect,mocs_to_orders,polygons_to_morton_mocs) stay function-shaped permanently — an offset-packed many-cover operation has no naturalself. Array-first consumers keep calling these directly, at zero wrapping cost. - The object is ergonomics.
Mocis a thin view over the canonicaluint64word array, never a new representation: every method is a single delegation to a kernel function. The array stays the interchange format —Moc.__morton_moc__()hands the canonical words back, and any object exposing that dunder is accepted wherever aMocis.
MOCpy crosswalk
HEALPix-MOC users already know these names, so the object mirrors them where they apply. The word encoding is mortie's own frozen grammar (specification §1/§4) either way — only the vocabulary is shared.
| MOCpy | mortie object | mortie kernel |
|---|---|---|
MOC.from_polygon(lon, lat, max_depth=…) |
Moc.from_polygon(lats, lons), or moc(geojson) |
morton_coverage_moc(lats, lons, order=…) |
a.union(b), a \| b |
a.union(b), a \| b |
moc_or(a, b) |
a.intersection(b), a & b |
a.intersection(b), a & b |
moc_and(a, b) |
a.difference(b), a - b |
a.difference(b), a - b |
moc_minus(a, b) |
a.symmetric_difference(b) |
a.symmetric_difference(b), a ^ b |
moc_xor(a, b) |
b.difference(a).empty() |
a.contains(b), b.within(a) |
moc_minus(b, a).size == 0 |
a.contains_lonlat(lon, lat) |
— (kernel only) | moc_intersects(a, geo2mort(lat, lon, order)) |
| — | a.intersects(b) |
moc_intersects(a, b) |
a.degrade_to_order(n).flatten() |
a.to_order(n) |
moc_to_order(a, n) |
a.complement() |
— (kernel only) | moc_not(a, domain) |
a.max_order |
repr(a) |
orders_of(a).max() |
Three places the vocabulary matches but the meaning does not:
from_polygontakes its coordinates the other way round. MOCpy isMOC.from_polygon(lon, lat, …); mortie isMoc.from_polygon(lats, lons, …). Same name, swapped order — transpose it and you get a cover somewhere else entirely.- MOCpy has no MOC-in-MOC
contains.MOC.contains(lon, lat, …)is a point-in-MOC mask (and is deprecated in favour ofcontains_lonlat/contains_skycoords); the MOCpy spelling of mortie'sa.contains(b)isb.difference(a).empty(). a.to_order(n)is notdegrade_to_orderand notflatten.degrade_to_order(n)returns a coarsened MOC andflatten()takes no order, so the MOCpy equivalent is the pairdegrade_to_order(n).flatten(). That covers the coarsening direction only:to_order(n)also densifies whennis finer than the cover, which MOCpy has no single call for.
Two mortie-specific notes. a.to_order(n) returns the flat array, not a Moc: a
single-order cell list is not a MOC, and re-normalizing it would collapse it
straight back to the compact form. And the predicates are cover algebra, not
polygon algebra — the conservative-direction table below says which way each
answer can err near a boundary.
The geometry-first Moc object over the MOC kernel (issue #196).
mortie's coverage surface is two layers, and the split is deliberate:
- the kernel — the free
moc_*functions in :mod:mortie._moc, words in and words out, unchanged and un-deprecated. Array-first consumers (moczarr's mask internals, zagg's grids, the batch shardmap machinery) keep calling them on plain ndarrays at zero wrapping cost, and the plural batch forms (:func:~mortie.batch.mocs_and, :func:~mortie.batch.mocs_intersect, :func:~mortie.batch.mocs_to_orders, :func:~mortie.batch.polygons_to_morton_mocs) stay function-shaped permanently — an offset-packed many-cover operation has no naturalself. - the object — :class:
Moc, here. It is ergonomics and nothing else: a thin view over the canonicaluint64word array, never a new representation. Every method is a single delegation to a kernel function. There is no algebra in this module; a method body that is not one kernel call is a bug, not a feature.
The interchange format stays the array. :meth:Moc.__morton_moc__ hands back
the canonical words, and any object exposing that dunder is accepted wherever a
Moc is — mortie owns geometry and words, downstream stores own their own
encodings, and the two meet at a plain uint64 array with neither importing
the other's private grammar.
Conservative directions. Every predicate below is cover algebra, not polygon algebra. A cover dilates the polygon it was built from — a boundary cell is included when it only partly overlaps — so the covered area is always a superset of the polygon, on both sides of the comparison. Read the answers accordingly; each predicate's docstring points back here.
| call | the question it answers exactly | as a polygon question |
|---|---|---|
a.intersects(b) |
do the two covers share any area? | may say True for polygons that only come within a cell of each other; a False is decisive. The safe direction for "must I read this shard?" — never a false skip. |
a.contains(b) |
is every cell of b inside a? |
may say True when b's polygon pokes outside a's by less than a cell. Exact for the question that matters — "will a store whose coverage is a answer a query for b?" |
a.within(b) |
is every cell of a inside b? |
the mirror of contains; same caveat. |
| either side empty | an empty cover is contained in everything and intersects nothing | a.contains(empty) is True (vacuously — there is no cell of empty outside a) while a.intersects(empty) is False. The two predicates disagree here by definition, not by accident: ask contains about coverage and intersects about work to do. |
a == b |
identical canonical words | not geometric equality: two covers of the same polygon built at different tolerance / max_cells compare unequal. |
Moc
A multi-order coverage as an object — coverage geometry that reads as geometry.
A thin view over the canonical uint64 word array and nothing more: the
words are the MOC, this class is the ergonomics, and every method is a
single delegation to the kernel function of the same meaning (see the
module docstring for the two-layer rule and the conservative-direction
table the predicates share).
Coverage is multi-order by default — coarse cells inside, fine cells
along the boundary, down to :func:~mortie.morton_coverage_moc's default
finest order — so there is no order argument. Use :meth:to_order to
cast to a flat single-order cell list when a consumer's grid wants one, and
read :func:repr to see what resolution you actually got. Construction is
deterministic: the same input through the same mortie version yields
the same words, byte for byte.
The words are normalized eagerly (:func:~mortie.compress_moc) and stored
read-only, which makes == and :func:hash well defined; the instance
itself is immutable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
dict or array_like or Moc
|
What to cover. A GeoJSON mapping ( |
required |
tolerance
|
float
|
Stop refining a boundary cell once its angular radius (in degrees)
drops to this value; see :func: |
None
|
max_cells
|
int
|
Best-first cell budget for the boundary; see
:func: |
None
|
latitude
|
str
|
Latitude convention of the input vertices (default |
'authalic'
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
See Also
mortie.morton_coverage_moc : the coverage kernel this wraps. mortie.compress_moc : the eager normalization applied to every instance.
Examples:
>>> import mortie
>>> aoi = {"type": "Polygon", "coordinates": [[
... [-76.56, 38.87], [-76.50, 38.87], [-76.50, 38.91],
... [-76.56, 38.91], [-76.56, 38.87]]]}
>>> region = mortie.moc(aoi)
>>> region.contains(region)
True
>>> region.to_order(9).dtype
dtype('uint64')
Source code in mortie/moc_object.py
357 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 409 410 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 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 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 540 541 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 573 574 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 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 | |
__delattr__(name)
Refuse attribute deletion -- a Moc is immutable.
Source code in mortie/moc_object.py
446 447 448 | |
__eq__(other)
Compare canonical words -- word identity, not geometric equality.
Source code in mortie/moc_object.py
655 656 657 658 659 | |
__hash__()
Hash the canonical words; sound because a Moc is immutable.
Source code in mortie/moc_object.py
661 662 663 | |
__iter__()
Iterate the cover's morton words.
Source code in mortie/moc_object.py
669 670 671 | |
__len__()
Count the cells in the cover.
Source code in mortie/moc_object.py
665 666 667 | |
__morton_moc__()
Canonical morton words — the interchange protocol (issue #196).
Returns:
| Type | Description |
|---|---|
ndarray
|
The read-only |
Source code in mortie/moc_object.py
645 646 647 648 649 650 651 652 653 | |
__reduce__()
Rebuild through the constructor -- pickle and copy cannot set slots.
A __slots__ class is restored by assigning its slot state, which
the immutability guard above refuses; reconstructing from the words
instead keeps Moc picklable (workers marshal their arguments) and
deep-copyable at the cost of one compress_moc on already-compact
words.
Returns:
| Type | Description |
|---|---|
tuple
|
The |
Source code in mortie/moc_object.py
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 | |
__repr__()
Show the cell count and the orders actually present.
Source code in mortie/moc_object.py
673 674 675 676 677 678 679 680 681 682 683 684 | |
__setattr__(name, value)
Refuse attribute assignment -- a Moc is immutable.
Source code in mortie/moc_object.py
440 441 442 443 444 | |
contains(other)
Whether every cell of other lies inside this cover.
Cover algebra, not polygon algebra — see the module docstring's
conservative-direction table. This is the "will a store whose
coverage is self answer a query for other?" test.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other
|
Moc or array_like
|
The cover to test for containment. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
Source code in mortie/moc_object.py
520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 | |
difference(other)
Cells in this cover but not other (MOCpy difference, -).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other
|
Moc or array_like
|
The cover to subtract. |
required |
Returns:
| Type | Description |
|---|---|
Moc
|
The difference cover |
Source code in mortie/moc_object.py
587 588 589 590 591 592 593 594 595 596 597 598 599 600 | |
from_polygon(lats, lons, tolerance=None, max_cells=None, *, latitude='authalic')
classmethod
Cover a polygon given as vertex latitudes and longitudes.
The MOCpy-vocabulary spelling of the coverage constructor, for callers
who already have (lats, lons) rather than GeoJSON. Multipart /
holes take the list-of-rings form, exactly as
:func:~mortie.morton_coverage_moc documents.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lats
|
array_like
|
Vertex latitudes / longitudes in degrees (one ring), or a list of such arrays for the multipart form. |
required |
lons
|
array_like
|
Vertex latitudes / longitudes in degrees (one ring), or a list of such arrays for the multipart form. |
required |
tolerance
|
float
|
Angular stop criterion in degrees. |
None
|
max_cells
|
int
|
Best-first cell budget for the boundary. |
None
|
latitude
|
str
|
Latitude convention of the input vertices (default
|
'authalic'
|
Returns:
| Type | Description |
|---|---|
Moc
|
The polygon's multi-order cover. |
Source code in mortie/moc_object.py
466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 | |
intersection(other)
Cells in both this cover and other (MOCpy intersection, &).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other
|
Moc or array_like
|
The cover to intersect with. |
required |
Returns:
| Type | Description |
|---|---|
Moc
|
The intersection cover. |
Source code in mortie/moc_object.py
572 573 574 575 576 577 578 579 580 581 582 583 584 585 | |
intersects(other)
Whether this cover and other share any area, at any order.
Cover algebra, not polygon algebra — see the module docstring's
conservative-direction table: True can mean "within a cell of each
other", False is decisive.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other
|
Moc or array_like
|
The cover to test against. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
Source code in mortie/moc_object.py
501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 | |
symmetric_difference(other)
Cells in exactly one of the two covers (MOCpy symmetric_difference, ^).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other
|
Moc or array_like
|
The cover to compare against. |
required |
Returns:
| Type | Description |
|---|---|
Moc
|
The symmetric-difference cover. |
Source code in mortie/moc_object.py
602 603 604 605 606 607 608 609 610 611 612 613 614 615 | |
to_order(order, max_cells=_FLAT_COVER_WARN_THRESHOLD)
Cast to a flat list of cells at one fixed order.
The consumer-grid direction: a multi-order cover in, every cell at
order out, as the canonical uint64 array — a flat
single-order cell list is not a MOC, and re-normalizing it would
collapse it straight back to the compact form.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
order
|
int
|
Target HEALPix order (0-29). |
required |
max_cells
|
int or None
|
Pre-emptive budget on the densified cell count; see
:func: |
_FLAT_COVER_WARN_THRESHOLD
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Sorted 1-D array of flat morton cells at |
Source code in mortie/moc_object.py
622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 | |
union(other)
Cells in this cover or in other (MOCpy union, |).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other
|
Moc or array_like
|
The cover to union with. |
required |
Returns:
| Type | Description |
|---|---|
Moc
|
The union cover. |
Source code in mortie/moc_object.py
557 558 559 560 561 562 563 564 565 566 567 568 569 570 | |
within(other)
Whether every cell of this cover lies inside other.
The mirror of :meth:contains; the same conservative direction
applies (module docstring).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
other
|
Moc or array_like
|
The cover to test containment against. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
Source code in mortie/moc_object.py
539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 | |