{"id":34020357,"url":"https://github.com/tebartsch/rportion","last_synced_at":"2026-03-08T23:33:15.830Z","repository":{"id":49738474,"uuid":"517508345","full_name":"tebartsch/rportion","owner":"tebartsch","description":"Represent unions of rectangular regions in the plane and efficiently obtain all maximum empty/used rectangles.","archived":false,"fork":false,"pushed_at":"2024-04-02T12:55:51.000Z","size":1191,"stargazers_count":5,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-12-15T03:53:37.134Z","etag":null,"topics":["interval","polygon","python","rectangle"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tebartsch.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-07-25T03:48:01.000Z","updated_at":"2025-01-07T11:48:33.000Z","dependencies_parsed_at":"2022-08-26T08:31:32.086Z","dependency_job_id":null,"html_url":"https://github.com/tebartsch/rportion","commit_stats":null,"previous_names":["tilmann-bartsch/rportion"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/tebartsch/rportion","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tebartsch%2Frportion","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tebartsch%2Frportion/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tebartsch%2Frportion/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tebartsch%2Frportion/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tebartsch","download_url":"https://codeload.github.com/tebartsch/rportion/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tebartsch%2Frportion/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30276982,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-08T20:45:49.896Z","status":"ssl_error","status_checked_at":"2026-03-08T20:45:49.525Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["interval","polygon","python","rectangle"],"created_at":"2025-12-13T15:00:42.828Z","updated_at":"2026-03-08T23:33:15.790Z","avatar_url":"https://github.com/tebartsch.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# rportion - data structure and operations for rectilinear polygons\n\n\n[![PyPI pyversions](https://img.shields.io/pypi/pyversions/rportion)](https://pypi.org/project/rportion/)\n[![Tests](https://github.com/tilmann-bartsch/rportion/actions/workflows/test.yaml/badge.svg?branch=master)](https://github.com/tilmann-bartsch/portion/actions/workflows/test.yaml)\n[![Coverage Status](https://coveralls.io/repos/github/tilmann-bartsch/rportion/badge.svg?branch=master)](https://coveralls.io/github/tilmann-bartsch/rportion?branch=master)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Commits](https://img.shields.io/github/last-commit/tilmann-bartsch/rportion/master)](https://github.com/tilmann-bartsch/rportion/commits/master)\n\nThe `rportion` library provides data structure to represent \n2D [rectilinear polygons](https://en.wikipedia.org/wiki/Rectilinear_polygon) (unions of 2D-intervals) in Python 3.9+.\nIt is built upon the library [`portion`](https://github.com/AlexandreDecan/portion) and follows its concepts.\nThe following features are provided:\n\n - 2D-Intervals (rectangles) which can be open/closed and finite/infinite at every boundary\n - intersection, union, complement and difference of rectilinear polygons\n - iterator over all maximum rectangles inside and outside a given polygon\n\nIn the case of integers/floats it can be used to keep track of the area resulting \nfrom the union/difference of rectangles:\n\n\u003cp align=\"center\"\u003e\n  \u003cimg width=\"65%\" src=\"https://github.com/tilmann-bartsch/rportion/raw/master/docu/simple-example_solid.gif\"\u003e\n\u003c/p\u003e\n\nInternally the library uses an [interval tree](https://en.wikipedia.org/wiki/Interval_tree) to represent a polygon.\n\n## Table of contents\n\n  * [Installation](#installation)\n  * [Documentation \u0026 usage](#documentation--usage)\n      * [Polygon creation](#polygon-creation)\n      * [Polygon bounds \u0026 attributes](#polygon-bounds--attributes)\n      * [Polygon operations](#polygon-operations)\n      * [Rectangle partitioning iterator](#rectangle-partitioning-iterator)\n      * [Maximum rectangle iterator](#maximum-rectangle-iterator)\n      * [Boundary](#boundary)\n      * [Internal data structure](#internal-data-structure)\n  * [Changelog](#changelog)\n  * [Contributions](#contributions)\n  * [License](#license)\n\n## Installation\n\n`rportion` can be installed from [PyPi](https://pypi.org/project/rportion/) with `pip` using \n\n```bash\npip install rportion\n```\n\nAlternatively, clone the repository and run\n\n```bash\npip install -e \".[test]\"\npython -m unittest discover -s tests\n```\n\n## Documentation \u0026 usage\n\n### Polygon creation\n\nAtomic polygons (rectangles) can be created by one of the following:\n```python\n\u003e\u003e\u003e import rportion as rp\n\u003e\u003e\u003e rp.ropen(0, 2, 0, 1)\n(x=(0,2), y=(0,1))\n\u003e\u003e\u003e rp.rclosed(0, 2, 0, 1)\n(x=[0,2], y=[0,1])\n\u003e\u003e\u003e rp.ropenclosed(0, 2, 0, 1)\n(x=(0,2], y=(0,1])\n\u003e\u003e\u003e rp.rclosedopen(0, 2, 0, 1)\n(x=[0,2), y=[0,1))\n\u003e\u003e\u003e rp.rsingleton(0, 1)\n(x=[0], y=[1])\n\u003e\u003e\u003e rp.rempty()\n(x=(), y=())\n```\n\nPolygons can also be created by using two intervals of the underlying library \n[`portion`](https://github.com/AlexandreDecan/portion):\n```python\n\u003e\u003e\u003e import portion as P\n\u003e\u003e\u003e import rportion as rp\n\u003e\u003e\u003e rp.RPolygon.from_interval_product(P.openclosed(0, 2), P.closedopen(0, 1))\n(x=(0,2], y=[0,1))\n```\n\n[\u0026uparrow; back to top](#table-of-contents)\n### Polygon bounds \u0026 attributes\n\nAn `RPolygon` defines the following properties\n - `empty` is true if the polygon is empty.\n   ```python\n   \u003e\u003e\u003e rp.rclosed(0, 2, 1, 2).empty\n   False\n   \u003e\u003e\u003e rp.rempty().empty\n   True\n   ```\n - `atomic` is true if the polygon can be expressed by a single rectangle.\n   ```python\n   \u003e\u003e\u003e rp.rempty().atomic\n   True\n   \u003e\u003e\u003e rp.rclosedopen(0, 2, 1, 2).atomic\n   True\n   \u003e\u003e\u003e (rp.rclosed(0, 2, 1, 2) | rp.rclosed(0, 2, 1, 3)).atomic\n   True\n   \u003e\u003e\u003e (rp.rclosed(0, 2, 1, 2) | rp.rclosed(1, 2, 1, 3)).atomic\n   False\n   ```\n - `enclosure` is the smallest rectangle containing the polygon.\n   ```python\n   \u003e\u003e\u003e (rp.rclosed(0, 2, 0, 2) | rp.rclosed(1, 3, 0, 1)).enclosure\n   (x=[0,3], y=[0,2])\n   \u003e\u003e\u003e (rp.rclosed(0, 1, -3, 3) | rp.rclosed(-P.inf, P.inf, -1, 1)).enclosure\n   (x=(-inf,+inf), y=[-3,3])\n   ```\n - `enclosure_x_interval` is the smallest rectangle containing the polygon's extension in x-dimension.\n   ```python\n   \u003e\u003e\u003e (rp.rclosed(0, 2, 0, 2) | rp.rclosed(1, 3, 0, 1)).x_enclosure_interval\n   x=[0,3]\n   \u003e\u003e\u003e (rp.rclosed(0, 1, -3, 3) | rp.rclosed(-P.inf, P.inf, -1, 1)).x_enclosure_interval\n   (-inf,+inf)\n   ```\n - `enclosure_y_interval` is the smallest interval containing the polygon's extension in y-dimension.\n   ```python\n   \u003e\u003e\u003e (rp.rclosed(0, 2, 0, 2) | rp.rclosed(1, 3, 0, 1)).y_enclosure_interval\n   [0,2]\n   \u003e\u003e\u003e (rp.rclosed(0, 1, -3, 3) | rp.rclosed(-P.inf, P.inf, -1, 1)).y_enclosure_interval\n   [-3,3]\n   ```\n - `x_lower`, `x_upper`, `y_lower` and `y_upper` yield the boundaries of the rectangle enclosing\n   the polygon.\n   ```python\n   \u003e\u003e\u003e p = rp.rclosedopen(0, 2, 1, 3)\n   \u003e\u003e\u003e p.x_lower, p.x_upper, p.y_lower, p.y_upper\n   (0, 2, 1, 3)\n   ```\n - `x_left`, `x_right`, `y_left` and `y_right` yield the type of the boundaries of the rectangle enclosing\n    the polygon.\n    ```python\n    \u003e\u003e\u003e p = rp.rclosedopen(0, 2, 1, 3)\n    \u003e\u003e\u003e p.x_left, p.x_right, p.y_left, p.y_right\n    (CLOSED, OPEN, CLOSED, OPEN)\n    ```\n\n\n[\u0026uparrow; back to top](#table-of-contents)\n### Polygon operations\n\n`RPolygon` instances support the following operations:\n - `p.intersection(other)` and `p \u0026 other` return the intersection of two rectilinear polygons.\n   ```python\n   \u003e\u003e\u003e rp.rclosed(0, 2, 0, 2) \u0026 rp.rclosed(1, 3, 0, 1)\n   (x=[1,2], y=[0,1])\n   ```\n - `p.union(other)` and `p | other` return the union of two rectilinear polygons.\n   ```python\n   \u003e\u003e\u003e rp.rclosed(0, 2, 0, 2) | rp.rclosed(1, 3, 0, 1)\n   (x=[0,3], y=[0,1]) | (x=[0,2], y=[0,2])\n   ```\n   Note that the resulting polygon is represented by the union of all maximal rectangles contained in\n   in the polygon, see [Maximum rectangle iterators](#maximum-rectangle-iterators).\n - `p.complement()` and `~p` return the complement of the rectilinear polygon.\n   ```python\n   \u003e\u003e\u003e ~rp.ropen(-P.inf, 0, -P.inf, P.inf)\n   ((x=[0,+inf), y=(-inf,+inf))\n   ```\n - `p.difference(other)` and `p - other` return the difference of two rectilinear polygons.\n   ```python\n   rp.rclosed(0, 3, 0, 2) - rp.ropen(2, 4, 1, 3)\n   (x=[0,3], y=[0,1]) | (x=[0,2], y=[0,2])\n   ```\n   Note that the resulting polygon is represented by the union of all maximal rectangles contained in\n   in the polygon, see [Maximum rectangle iterators](#maximum-rectangle-iterators).\n\n[\u0026uparrow; back to top](#table-of-contents)\n### Rectangle partitioning iterator\n\nThe method `rectangle_partitioning` of a `RPolygon` instance returns an iterator\nover rectangles contained in the rectilinear polygon which disjunctively cover it. I.e.\n\n```python\n\u003e\u003e\u003e poly = rp.rclosedopen(2, 5, 1, 4) | rp.rclosedopen(1, 8, 2, 3) | rp.rclosedopen(6, 8, 1, 3)\n\u003e\u003e\u003e poly = poly - rp.rclosedopen(4, 7, 2, 4)\n\u003e\u003e\u003e list(poly.rectangle_partitioning())\n[(x=[1,4), y=[2,3)), (x=[2,5), y=[1,2)), (x=[6,8), y=[1,2)), (x=[2,4), y=[3,4)), (x=[7,8), y=[2,3))]\n```\n\nwhich can be visualized as follows:\n\u003cp align=\"center\"\u003e\n  \u003cimg width=\"95%\" src=\"https://github.com/tilmann-bartsch/rportion/raw/master/docu/simple-example_partitioning.png\"\u003e\n\u003c/p\u003e\n\n**Left:** Simple Rectilinear polygon. The red areas are part of the polygon.\u003cbr\u003e\n**Right:** Rectangles in the portion are shown with black borderlines. As it is visible \n           `rectangle_partitioning` prefers rectangles with long x-interval over \n           rectangles with long y-interval.\n           \n\n\n[\u0026uparrow; back to top](#table-of-contents)\n### Maximum rectangle iterator\n\nThe method `maximal_rectangles` of a `RPolygon` instance returns an iterator over all maximal rectangles contained\nin the rectilinear polygon.\n\nA maximal rectangle is rectangle in the polygon which is not a real subset of any other rectangle contained in\nthe rectilinear polygon. I.e. \n\n```python\n\u003e\u003e\u003e poly = rp.rclosedopen(2, 5, 1, 4) | rp.rclosedopen(1, 8, 2, 3) | rp.rclosedopen(6, 8, 1, 3)\n\u003e\u003e\u003e poly = poly - rp.rclosedopen(4, 7, 2, 4)\n\u003e\u003e\u003e list(poly.maximal_rectangles())\n[(x=[1, 4), y = [2, 3)), (x=[2, 5), y = [1, 2)), (x=[6, 8), y = [1, 2)), (x=[2, 4), y = [1, 4)), (x=[7, 8), y = [1, 3))]\n```\nwhich can be visualized as follows:\n\u003cp align=\"center\"\u003e\n  \u003cimg width=\"95%\" src=\"https://github.com/tilmann-bartsch/rportion/raw/master/docu/simple-example_max-rectangles.png\"\u003e\n\u003c/p\u003e\n\n**Left:** Simple Rectilinear polygon. The red areas are part of the polygon.\u003cbr\u003e\n**Right:** Maximal contained rectangles are drawn above each other transparently.\n\n[\u0026uparrow; back to top](#table-of-contents)\n## Boundary\n\nThe method `boundary` of a `RPolygon` instance returns another `RPolygon` instance representing the boundary of\nthe polygon. I.e.\n\n```python\n\u003e\u003e\u003e poly = rp.closed(0, 1, 2, 3)\n\u003e\u003e\u003e poly.boundary()\n(x=[1,2], y=[3]) | (x=[1,2], y=[4]) | (x=[1], y=[3,4]) | (x=[2], y=[3,4])\n```\n\n[\u0026uparrow; back to top](#table-of-contents)\n## Internal data structure\n\nThe polygon is internally stored using an [interval tree](https://en.wikipedia.org/wiki/Interval_tree). Every\nnode of the tree corresponds to an interval in x-dimension which is representable by boundaries (in x-dimension) \npresent in the polygon. Each node contains an 1D-interval (by using the library\n[`portion`](https://github.com/AlexandreDecan/portion)) in y-dimension. Combining those 1D-intervals\nyields a rectangle contained in the polygon.\n\nI.e. for the rectangle `(x=[0, 2), y=[1, 3))` this can be visualized as follows.\n```\n  interval tree with      x-interval corresponding       y-interval stored in\n a lattice-like shape             to each node                each node\n       ┌─x─┐                      ┌─(-∞,+∞)─┐                  ┌─()──┐\n       │   │                      │         │                  │     │\n     ┌─x─┬─x─┐               ┌─(-∞,2)──┬──[0,+∞)─┐          ┌─()──┬──()─┐\n     │   │   │               │         │         │          │     │     │\n     x   x   x            (-∞,0]     [0,2)     [2,+∞)      ()   [1,3)   ()\n```\nThe class `RPolygon` used this model by holding three data structures.\n  - `_x_boundaries`: Sorted list of necessary boundaries in x-dimension with type (`OPEN` or `CLOSED`)\n  - `_used_y_ranges`: List of lists in a triangular shape representing the interval tree for the\n                      space occupied by the rectilinear polygon.\n  - `_free_y_ranges`: List of list in a triangular shape representing the interval tree of\n                      for the space not occupied by the rectilinear polygon.\n\nNote that a separate data structure for the area outside the polygon is kept.\nThis is done in order to be able to obtain the complement of a polygon efficiently.\n\nFor the example shown above this is:\n```python\n\u003e\u003e\u003e poly = rp.rclosedopen(0, 2, 1, 3)\n\u003e\u003e\u003e poly._x_boundaries\nSortedList([(-inf, OPEN), (0, OPEN), (2, OPEN), (+inf, OPEN)])\n\u003e\u003e\u003e poly._used_y_ranges\n[[(), (), ()], \n [(), [1,3)], \n [()]]\n\u003e\u003e\u003e poly._free_y_ranges\n[[(-inf,1) | [3,+inf), (-inf,1) | [3,+inf), (-inf,+inf)], \n [(-inf,1) | [3,+inf), (-inf,1) | [3,+inf)], \n [(-inf,+inf)]]\n```\n\nYou can use the function `data_tree_to_string` as noted below to print the internal data structure in a tabular format:\n\n```python\n\u003e\u003e\u003e poly = rp.rclosedopen(0, 2, 1, 3)\n\u003e\u003e\u003e print(data_tree_to_string(poly._x_boundaries, poly._used_y_ranges, 6))\n                |  +inf     2     0\n----------------+------------------\n     -inf (OPEN)|    ()    ()    ()\n      0 (CLOSED)|    () [1,3)\n      2 (CLOSED)|    ()\n```\n\n```python\n\u003e\u003e\u003e poly = rp.rclosedopen(2, 5, 1, 4) | rp.rclosedopen(1, 8, 2, 3) | rp.rclosedopen(6, 8, 1, 3)\n\u003e\u003e\u003e poly = poly - rp.rclosedopen(4, 7, 2, 4)\n\u003e\u003e\u003e print(data_tree_to_string(poly._x_boundaries, poly._used_y_ranges, 6))\n                |  +inf     8     7     6     5     4     2     1\n----------------+------------------------------------------------\n     -inf (OPEN)|    ()    ()    ()    ()    ()    ()    ()    ()\n      1 (CLOSED)|    ()    ()    ()    ()    () [2,3) [2,3)\n      2 (CLOSED)|    ()    ()    ()    () [1,2) [1,4)\n      4 (CLOSED)|    ()    ()    ()    () [1,2)\n      5 (CLOSED)|    ()    ()    ()    ()\n      6 (CLOSED)|    () [1,2) [1,2)\n      7 (CLOSED)|    () [1,3)\n```\n\n```python\ndef data_tree_to_string(x_boundaries,\n                        y_intervals,\n                        spacing: int):\n    col_space = 10\n    n = len(y_intervals)\n    msg = \" \" * (spacing + col_space) + \"|\"\n    for x_b in x_boundaries[-1:0:-1]:\n        msg += f\"{str(x_b.val):\u003e{spacing}}\"\n    msg += \"\\n\" + f\"-\" * (spacing+col_space) + \"+\"\n    for i in range(n):\n        msg += f\"-\" * spacing\n    msg += \"\\n\"\n    for i, row in enumerate(y_intervals):\n        x_b = x_boundaries[i]\n        msg += f\"{str((~x_b).val) + ' (' + str((~x_b).btype) + ')':\u003e{spacing+ col_space}}|\"\n        for val in row:\n            msg += f\"{str(val):\u003e{spacing}}\"\n        msg += \"\\n\"\n    return msg\n```\n\n[\u0026uparrow; back to top](#table-of-contents)\n## Changelog\nThis library adheres to a [semantic versioning](https://semver.org/) scheme.\nSee [CHANGELOG.md](https://github.com/tilmann-bartsch/rportion/blob/master/CHANGELOG.md) for the list of changes.\n\n## Contributions\nContributions are very welcome! Feel free to report bugs or suggest new features using GitHub issues and/or pull requests.\n\n## License\nDistributed under [MIT License](https://github.com/tilmann-bartsch/rportion/blob/master/LICENSE).","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftebartsch%2Frportion","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftebartsch%2Frportion","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftebartsch%2Frportion/lists"}