<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ******************************************************************************
#
#  Project:  GDAL
#  Purpose:  rectangle class (x, y, width, height)
#  Author:   Idan Miara, &lt;idan@miara.com&gt;
#
# ******************************************************************************
#  Copyright (c) 2020, Idan Miara &lt;idan@miara.com&gt;
#
#  Permission is hereby granted, free of charge, to any person obtaining a
#  copy of this software and associated documentation files (the "Software"),
#  to deal in the Software without restriction, including without limitation
#  the rights to use, copy, modify, merge, publish, distribute, sublicense,
#  and/or sell copies of the Software, and to permit persons to whom the
#  Software is furnished to do so, subject to the following conditions:
#
#  The above copyright notice and this permission notice shall be included
#  in all copies or substantial portions of the Software.
#
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
#  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
#  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
#  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
#  DEALINGS IN THE SOFTWARE.
# ******************************************************************************


class GeoRectangle:
    __slots__ = ["x", "y", "w", "h"]

    def __init__(self, x, y, w, h, allow_negative_size=False):
        if w &lt;= 0:
            if allow_negative_size:
                x = x + w
                w = -w
            else:
                w = 0
        if h &lt;= 0:
            if allow_negative_size:
                y = y + h
                h = -h
            else:
                h = 0
        self.x = x
        self.y = y
        self.w = w
        self.h = h

    def __eq__(self, other):
        if not isinstance(other, GeoRectangle):
            # don't attempt to compare against unrelated types
            return False
        return self.xywh == other.xywh

    def __round__(self, *args, **kwargs):
        return self.from_lrdu(*(round(i, *args, **kwargs) for i in self.lrdu))

    def is_empty(self):
        return self.w &lt;= 0 or self.h &lt;= 0

    def intersect(self, other: "GeoRectangle"):
        return GeoRectangle.from_min_max(
            max(self.min_x, other.min_x),
            min(self.max_x, other.max_x),
            max(self.min_y, other.min_y),
            min(self.max_y, other.max_y),
        )

    def union(self, other: "GeoRectangle"):
        return GeoRectangle.from_min_max(
            min(self.min_x, other.min_x),
            max(self.max_x, other.max_x),
            min(self.min_y, other.min_y),
            max(self.max_y, other.max_y),
        )

    def round(self, digits):
        self.x = round(self.x, digits)
        self.y = round(self.y, digits)
        self.w = round(self.w, digits)
        self.h = round(self.h, digits)

    def align(self, geo_transform):
        # compute the pixel-aligned bounding box (larger than the feature's bbox)
        left = self.min_x - (self.min_x - geo_transform[0]) % geo_transform[1]
        right = self.max_x + (
            geo_transform[1] - ((self.max_x - geo_transform[0]) % geo_transform[1])
        )
        bottom = self.min_y + (
            geo_transform[5] - ((self.min_y - geo_transform[3]) % geo_transform[5])
        )
        top = self.max_y - (self.max_y - geo_transform[3]) % geo_transform[5]
        return self.from_lrud(left, right, top, bottom)

    def get_partition(self, part: "GeoRectangle"):
        # part: x,y - part indexes; w,h - part counts
        part_width = self.w / part.w
        part_hight = self.h / part.h
        return GeoRectangle(
            self.x + part.x * part_width,
            self.y + part.y * part_hight,
            part_width,
            part_hight,
        )

    @classmethod
    def empty(cls):
        return cls(0, 0, 0, 0)

    @classmethod
    def from_lrud(cls, l, r, u, d):
        ret = cls(l, d, r - l, u - d)
        return ret

    @classmethod
    # same as min_max
    def from_lrdu(cls, l, r, d, u):
        ret = cls(l, d, r - l, u - d)
        return ret

    @classmethod
    def from_lurd(cls, l, u, r, d):
        """from projwin (minx maxy maxx miny) == (ulx uly lrx lry) == (l u r d)"""
        ret = cls(l, d, r - l, u - d)
        return ret

    @classmethod
    # same as min_max
    def from_xwyh(cls, x, w, y, h, allow_negative_size=False):
        ret = cls(x, y, w, h, allow_negative_size)
        return ret

    @classmethod
    # # same as cls
    def from_xywh(cls, x, y, w, h, allow_negative_size=False):
        ret = cls(x, y, w, h, allow_negative_size)
        return ret

    @classmethod
    # # same as cls
    def from_xywhps(cls, x, y, w, h, px, py):
        ret = cls(x, y, w * px, h * py, True)
        return ret

    @classmethod
    # same as lrdu
    def from_min_max(cls, min_x, max_x, min_y, max_y):
        ret = cls(min_x, min_y, max_x - min_x, max_y - min_y)
        return ret

    @classmethod
    def from_center_and_radius(cls, cent_x, cent_y, rad_x, rad_y=None):
        if rad_y is None:
            rad_y = rad_x
        x = cent_x - rad_x
        y = cent_y - rad_y
        w = rad_x * 2
        h = rad_y * 2
        ret = cls(x, y, w, h)
        return ret

    @classmethod
    def from_points(cls, points):
        return cls.from_min_max(
            min(p[0] for p in points),
            max(p[0] for p in points),
            min(p[1] for p in points),
            max(p[1] for p in points),
        )

    @classmethod
    def from_geotransform_and_size(cls, gt, size):
        if gt[2] or gt[4]:
            return cls.from_points(get_points_extent(gt, *size))
        else:
            # faster method
            origin = (gt[0], gt[3])
            pixel_size = (gt[1], gt[5])
            extent = cls.from_xywhps(*origin, *size, *pixel_size)
            # extent_b = cls.from_points(get_points_extent(gt, *size))
            # assert extent == extent_b
            return extent

    def to_pixels(self, pixel_size):
        return self.from_xwyh(
            self.x / pixel_size[0],
            self.w / pixel_size[0],
            self.y / pixel_size[1],
            self.h / pixel_size[1],
            True,
        )

    @classmethod
    def from_geotransform_and_size_to_pix(cls, gt, size):
        origin = (gt[0], gt[3])
        pixel_size = (gt[1], gt[5])
        pix_origin = list(origin[i] / pixel_size[i] for i in (0, 1))
        # pix_bounds = list(origin[i] / pixel_size[i] + size[i] for i in (0, 1))
        return cls.from_xwyh(pix_origin[0], size[0], pix_origin[1], size[1])

    @property
    def area(self):
        return self.w * self.h

    @property
    def size(self):
        return self.w, self.h

    @property
    def left(self):
        return self.x

    @property
    def right(self):
        return self.x + self.w

    @property
    def down(self):
        return self.y

    @property
    def up(self):
        return self.y + self.h

    @property
    def min_x(self):
        return self.x

    @property
    def max_x(self):
        return self.x + self.w

    @property
    def min_y(self):
        return self.y

    @property
    def max_y(self):
        return self.y + self.h

    @property
    def lurd(self):
        return self.left, self.up, self.right, self.down

    @property
    def lrud(self):
        return self.left, self.right, self.up, self.down

    @property
    def ldru(self):
        return self.left, self.down, self.right, self.up

    @property
    def lrdu(self):
        return self.left, self.right, self.down, self.up

    @property
    def xywh(self):
        return self.x, self.y, self.w, self.h

    @property
    def xwyh(self):
        return self.x, self.w, self.y, self.h

    @property
    def min_max(self):
        return self.min_x, self.max_x, self.min_y, self.max_y

    def __str__(self):
        return f"Rectangle(x[{self.min_x},{self.max_x}], y[{self.min_y},{self.max_y}] wh[{self.w},{self.h}])"

    def __repr__(self):
        return f"Rectangle(x:{self.x}, y:{self.y}, w:{self.w}, h:{self.h})"

    def __hash__(self):
        return hash(self.xywh)


def get_points_extent(gt, cols, rows):
    """Return list of corner coordinates from a geotransform"""

    def transform_point(px, py):
        x = gt[0] + (px * gt[1]) + (py * gt[2])
        y = gt[3] + (px * gt[4]) + (py * gt[5])
        return x, y

    return [
        transform_point(0, 0),
        transform_point(0, rows),
        transform_point(cols, rows),
        transform_point(cols, 0),
    ]
</pre></body></html>