Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/283.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/sorting/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Php 将多个相邻矩形合并为一个多边形_Php_Geometry_Polygons_Rectangles - Fatal编程技术网

Php 将多个相邻矩形合并为一个多边形

Php 将多个相邻矩形合并为一个多边形,php,geometry,polygons,rectangles,Php,Geometry,Polygons,Rectangles,背景:我在一个小型购物中心的网站上工作,该网站有多个矩形“单元”可供出租。当一个“商店”出现时,它可以租用一个或多个“单元”,我想生成一个由商店组成的地图(无未打印单元) 问题: 我有一个由点对定义的矩形(单位)列表—[[lefttop_x;lefttop_y];[rightbottom_x;rightbottom_y]]——我想将它们合并到多边形中,这样我就可以正确地设置它们的样式(然后通过Canvas/SVG/VML/Raphael.js进行渲染) 单位总是矩形 单位有不同的大小 单元总是

背景:我在一个小型购物中心的网站上工作,该网站有多个矩形“单元”可供出租。当一个“商店”出现时,它可以租用一个或多个“单元”,我想生成一个由商店组成的地图(无未打印单元)

问题

我有一个由点对定义的矩形(单位)列表—
[[lefttop_x;lefttop_y];[rightbottom_x;rightbottom_y]]
——我想将它们合并到多边形中,这样我就可以正确地设置它们的样式(然后通过Canvas/SVG/VML/Raphael.js进行渲染)

  • 单位总是矩形
  • 单位有不同的大小
  • 单元总是相邻的(它们之间没有空间)
作为这个(最好是PHP,但我可以处理伪代码)操作的结果,我希望有一个多边形点数组

多谢各位

旁白:我一直在研究这个问题,我发现了很多“接近我想要的”问题+答案,但我不是太累了,就是我与数学失去了联系太久:)

只是一些想法:

  • 迭代所有角落,找到一个只与一个单位发生关联的角落,从而找到你的工会的一个角落
  • 从中选择一个迭代方向,例如始终逆时针
  • 检查此方向的边是否与另一个单元的角入射,或者沿此边的下一个角是否与另一个单元的边入射。其中任何一个都将成为联盟的下一个角落。否则,此边的端点就是下一个角点
  • 继续以这种方式,从一个单元移动到下一个单元,直到到达起点

  • 我不会用数学来解决这个问题,只会分析

    考虑下图:

    在这里,我们同时有两个例子,以确保我们将涵盖每个案例

    • 在第一幅图中,我们有一个特殊情况:3、4、5、11、12、13号矩形创建了一个空白区域,在您的情况下,这可能是一个烟雾空间

    • 在第二幅图中,我们在16、17、18、19号矩形之间有一个角。。。这将在以后有其重要性

    我是如何解决问题的,使用了以下内容:

    • 角点是一个被写入2到8次的点:至少2次,因为如果我们想象一个矩形ABCD,角点B将与AB和BC共享(因此像素被放置了2次)。在矩形16、17、18、19的情况下,可以写入8次,其中一个点与4个矩形共享,因此有8条边

    • 边是一组点,可以写1到2次(不考虑角):如果边是单独的,不靠近另一侧,则写1次;如果边靠近另一侧,则写2次。不靠近另一侧的一侧靠近外侧:它应该是最终多边形的一部分

    这就是逻辑:

    • 我们创建了一个与整个图像大小相同的虚拟空间,填充了零(0)
    • 我们写入所有矩形,但不是写入像素,而是增加虚拟像素的值

                                21111111112                   
                                1         1                   
                                1         1                   
                                1         1                   
                                1         1                   
                                1         1                   
                                1         1                   
                                1         1                   
                                1         1                   
                                1         1                   
                      2111111111622222222261111111112         
                      1         2         2         1         
                      1         2         2         1         
                      1         2         2         1         
                      1         2         2         1         
                      1         2         2         1         
                      1         2         2         1         
                      1         2         2         1         
                      1         2         2         1         
                      1         2         2         1         
            21111111116222222222611111111141111111112         
            1         2         1                             
            1         2         1                             
            1         2         1                             
            1         2         1                             
            1         2         1                             
            1         2         1                             
            1         2         1                             
            1         2         1                             
            1         2         1                             
            (...)
      
    (抱歉,我的缩进似乎与SO的格式化工具有问题)

    • 我们删除所有值大于2的虚拟点,但角点设置为1的除外
    在这一点上,我们有多边形和点(在其他几个矩形的中间有一个角)

    • 现在我们需要寻找一个或几个多边形(当我们在11 12 13 14 3 4 5矩形的情况下,我们可能有几个多边形)。这意味着,在我们的虚拟图像中搜索一个点

    • 如果点是单独的(见上文),它在它的顶部、左边、底部或右侧没有点,这是一个角落(我们早些时候保存了我们的角落)在其他几个矩形的中间。这是相当棘手的,但如果所有的矩形都大于4像素,则可以使用

    • 当我们找到一个点时,我们存储它,尝试迭代一个方向(上/左/右/下),然后继续向这个方向移除点,直到没有更多的点:这是多边形的一个角。我们继续这样做,直到它不可能移动到任何方向:这意味着我们在多边形的末端

    • 现在,您将获得一个二维数组:第一个维度是多边形列表(在第一个示例中),第二个维度是描述多边形的点列表。对于每个多边形,您只需迭代这些点并将当前点连接到下一个点即可获得多边形

    现在结果如何

    实施:

    class PolygonMaker
    {
    
        private $image;
        private $width;
        private $height;
        private $vImage;
    
        public function __construct($width, $height)
        {
            // create a GD image to display results and debug
            $this->width = $width;
            $this->height = $height;
            $this->image = imagecreatetruecolor($width, $height);
            $white = imagecolorallocate($this->image, 0xFF, 0xFF, 0xFF);
            imagefill($this->image, 0, 0, $white);
            imagesetthickness($this->image, 3);
        }
    
        public function __destruct()
        {
            imagedestroy($this->image);
        }
    
        public function display()
        {
            // Display gd image as png
            header("Content-type: image/png");
            imagepng($this->image);
        }
    
        public function drawRectangles(array $rectangles, $r, $g, $b)
        {
            // Draw rectangles as they are inside the gd image
            foreach ($rectangles as $rectangle)
            {
                list($tx, $ty) = $rectangle[0];
                list($bx, $by) = $rectangle[1];
                $color = imagecolorallocate($this->image, $r, $g, $b);
                imagerectangle($this->image, $tx, $ty, $bx, $by, $color);
            }
        }
    
        public function findPolygonsPoints(array $rectangles)
        {
            // Create a virtual image where rectangles will be "drawn"
            $this->_createVirtualImage($rectangles);
    
            $polygons = array ();
    
            // Searches for all polygons inside the virtual image
            while (!is_null($beginPoint = $this->_findPolygon()))
            {
                $polygon = array ();
    
                // Push the first point
                $polygon[] = $this->_cleanAndReturnPolygonPoint($beginPoint);
                $point = $beginPoint;
    
                // Try to go up, down, left, right until there is no more point
                while ($point = $this->_getNextPolygonPoint($point))
                {
                    // Push the found point
                    $polygon[] = $this->_cleanAndReturnPolygonPoint($point);
                }
    
                // Push the first point at the end to close polygon
                $polygon[] = $beginPoint;
    
                // Add the polygon to the list, in case of several polygons in the image
                $polygons[] = $polygon;
            }
    
            $this->vImage = null;
            return $polygons;
        }
    
        private function _createVirtualImage(array $rectangles)
        {
            // Create a 0-filled grid where will be stored rectangles
            $this->vImage = array_fill(0, $this->height, array_fill(0, $this->width, 0));
    
            // Draw each rectangle to that grid (each pixel increments the corresponding value of the grid of 1)
            foreach ($rectangles as $rectangle)
            {
                list($x1, $y1, $x2, $y2) = array ($rectangle[0][0], $rectangle[0][1], $rectangle[1][0], $rectangle[1][1]);
                $this->_drawVirtualLine($x1, $y1, $x1, $y2); // top-left, bottom-left
                $this->_drawVirtualLine($x2, $y1, $x2, $y2); // top-right, bottom-right
                $this->_drawVirtualLine($x1, $y1, $x2, $y1); // top-left, top-right
                $this->_drawVirtualLine($x1, $y2, $x2, $y2); // bottom-left, bottom-right
            }
    
            // Remove all pixels that are scored > 1 (that's our logic!)
            for ($y = 0; ($y < $this->height); $y++)
            {
                for ($x = 0; ($x < $this->width); $x++)
                {
                    $value = &$this->vImage[$y][$x];
                    $value = $value > 1 ? 0 : $value;
                }
            }
        }
    
        private function _drawVirtualLine($x1, $y1, $x2, $y2)
        {
            // Draw a vertial line in the virtual image
            if ($x1 == $x2)
            {
                if ($y1 > $y2)
                {
                    list($x1, $y1, $x2, $y2) = array ($x2, $y2, $x1, $y1);
                }
                for ($y = $y1; ($y <= $y2); $y++)
                {
                    $this->vImage[$y][$x1]++;
                }
            }
    
            // Draw an horizontal line in the virtual image
            if ($y1 == $y2)
            {
                if ($x1 > $x2)
                {
                    list($x1, $y1, $x2, $y2) = array ($x2, $y2, $x1, $y1);
                }
                for ($x = $x1; ($x <= $x2); $x++)
                {
                    $this->vImage[$y1][$x]++;
                }
            }
    
            // Force corners to be 1 (because one corner is at least used 2 times but we don't want to remove them)
            $this->vImage[$y1][$x1] = 1;
            $this->vImage[$y1][$x2] = 1;
            $this->vImage[$y2][$x1] = 1;
            $this->vImage[$y2][$x2] = 1;
        }
    
        private function _findPolygon()
        {
            // We're looking for the first point in the virtual image
            foreach ($this->vImage as $y => $row)
            {
                foreach ($row as $x => $value)
                {
                    if ($value == 1)
                    {
                        // Removes alone points ( every corner have been set to 1, but some corners are alone (eg: middle  of 4 rectangles)
                        if ((!$this->_hasPixelAtBottom($x, $y)) && (!$this->_hasPixelAtTop($x, $y))
                           && (!$this->_hasPixelAtRight($x, $y)) && (!$this->_hasPixelAtLeft($x, $y)))
                        {
                            $this->vImage[$y][$x] = 0;
                            continue;
                        }
                        return array ($x, $y);
                    }
                }
            }
            return null;
        }
    
        private function _hasPixelAtBottom($x, $y)
        {
            // The closest bottom point is a point positionned at (x, y + 1)
            return $this->_hasPixelAt($x, $y + 1);
        }
    
        private function _hasPixelAtTop($x, $y)
        {
            // The closest top point is a point positionned at (x, y - 1)
            return $this->_hasPixelAt($x, $y - 1);
        }
    
        private function _hasPixelAtLeft($x, $y)
        {
            // The closest left point is a point positionned at (x - 1, y)
            return $this->_hasPixelAt($x - 1, $y);
        }
    
        private function _hasPixelAtRight($x, $y)
        {
            // The closest right point is a point positionned at (x + 1, y)
            return $this->_hasPixelAt($x + 1, $y);
        }
    
        private function _hasPixelAt($x, $y)
        {
            // Check if the pixel (x, y) exists
            return ((isset($this->vImage[$y])) && (isset($this->vImage[$y][$x])) && ($this->vImage[$y][$x] > 0));
        }
    
        private function _cleanAndReturnPolygonPoint(array $point)
        {
            // Remove a point from the virtual image
            list($x, $y) = $point;
            $this->vImage[$y][$x] = 0;
            return $point;
        }
    
        private function _getNextPolygonPoint(array $point)
        {
            list($x, $y) = $point;
    
            // Initialize modifiers, to move to the right, bottom, left or top.
            $directions = array(
                    array(1, 0), // right
                    array(0, 1), // bottom
                    array(-1, 0), // left
                    array(0, -1), // top
            );
    
            // Try to get to one direction, if we can go ahead, there is a following corner
            $return = null;
            foreach ($directions as $direction)
            {
                list($xModifier, $yModifier) = $direction;
                if (($return = $this->_iterateDirection($x, $y, $xModifier, $yModifier)) !== null)
                {
                    return $return;
                }
            }
    
            // the point is alone : we are at the end of the polygon
            return $return;
        }
    
        private function _iterateDirection($x, $y, $xModifier, $yModifier)
        {
            // This method follows points in a direction until the last point
            $return = null;
            while ($this->_hasPixelAt($x + $xModifier, $y + $yModifier))
            {
                $x = $x + $xModifier;
                $y = $y + $yModifier;
    
                // Important : we remove the point so we'll not get back when moving
                $return = $this->_cleanAndReturnPolygonPoint(array ($x, $y));
            }
    
            // The last point is a corner of the polygon because if it has no following point, we change direction
            return $return;
        }
    
        /**
         * This method draws a polygon with the given points. That's to check if
         * our calculations are valid.
         *
         * @param array $points An array of points that define the polygon
         */
        public function drawPolygon(array $points, $r, $g, $b)
        {
            $count = count($points);
            for ($i = 0; ($i < $count); $i++)
            {
                // Draws a line between the current and the next point until the last point is reached
                if (array_key_exists($i + 1, $points))
                {
                    list($x1, $y1) = $points[$i];
                    list($x2, $y2) = $points[$i + 1];
                    $black = imagecolorallocate($this->image, $r, $g, $b);
                    imageline($this->image, $x1, $y1, $x2, $y2, $black);
                }
            }
        }
    
    }
    

    以下是我的解决方案:

    RectUnion.php
    
    
    merge.php
    
    会前
    会后
    运行时间:秒
    边:
    要点:
    
    它是如何工作的?

    该脚本标识并删除所有“重叠”段。例如:

    首先,每个矩形的边在与径向矩形边的交点处分开。
    例如,考虑B矩形的B2-B3边:“分割边”方法将其拆分为B2-D1、D1-D4和D4-B3段。 然后,“RemovedUpplicates”方法删除所有重叠(重复)的段。
    例如,D1-D4段是重复的,因为它出现在B矩形和D矩形中。
    最后,“getSides”方法返回剩余线段的列表,而“getPoints”方法返回多边形点的列表。
    “draw”方法仅用于结果的图形表示,并且需要使用:

    关于性能的信息

    class PolygonMaker
    {
    
        private $image;
        private $width;
        private $height;
        private $vImage;
    
        public function __construct($width, $height)
        {
            // create a GD image to display results and debug
            $this->width = $width;
            $this->height = $height;
            $this->image = imagecreatetruecolor($width, $height);
            $white = imagecolorallocate($this->image, 0xFF, 0xFF, 0xFF);
            imagefill($this->image, 0, 0, $white);
            imagesetthickness($this->image, 3);
        }
    
        public function __destruct()
        {
            imagedestroy($this->image);
        }
    
        public function display()
        {
            // Display gd image as png
            header("Content-type: image/png");
            imagepng($this->image);
        }
    
        public function drawRectangles(array $rectangles, $r, $g, $b)
        {
            // Draw rectangles as they are inside the gd image
            foreach ($rectangles as $rectangle)
            {
                list($tx, $ty) = $rectangle[0];
                list($bx, $by) = $rectangle[1];
                $color = imagecolorallocate($this->image, $r, $g, $b);
                imagerectangle($this->image, $tx, $ty, $bx, $by, $color);
            }
        }
    
        public function findPolygonsPoints(array $rectangles)
        {
            // Create a virtual image where rectangles will be "drawn"
            $this->_createVirtualImage($rectangles);
    
            $polygons = array ();
    
            // Searches for all polygons inside the virtual image
            while (!is_null($beginPoint = $this->_findPolygon()))
            {
                $polygon = array ();
    
                // Push the first point
                $polygon[] = $this->_cleanAndReturnPolygonPoint($beginPoint);
                $point = $beginPoint;
    
                // Try to go up, down, left, right until there is no more point
                while ($point = $this->_getNextPolygonPoint($point))
                {
                    // Push the found point
                    $polygon[] = $this->_cleanAndReturnPolygonPoint($point);
                }
    
                // Push the first point at the end to close polygon
                $polygon[] = $beginPoint;
    
                // Add the polygon to the list, in case of several polygons in the image
                $polygons[] = $polygon;
            }
    
            $this->vImage = null;
            return $polygons;
        }
    
        private function _createVirtualImage(array $rectangles)
        {
            // Create a 0-filled grid where will be stored rectangles
            $this->vImage = array_fill(0, $this->height, array_fill(0, $this->width, 0));
    
            // Draw each rectangle to that grid (each pixel increments the corresponding value of the grid of 1)
            foreach ($rectangles as $rectangle)
            {
                list($x1, $y1, $x2, $y2) = array ($rectangle[0][0], $rectangle[0][1], $rectangle[1][0], $rectangle[1][1]);
                $this->_drawVirtualLine($x1, $y1, $x1, $y2); // top-left, bottom-left
                $this->_drawVirtualLine($x2, $y1, $x2, $y2); // top-right, bottom-right
                $this->_drawVirtualLine($x1, $y1, $x2, $y1); // top-left, top-right
                $this->_drawVirtualLine($x1, $y2, $x2, $y2); // bottom-left, bottom-right
            }
    
            // Remove all pixels that are scored > 1 (that's our logic!)
            for ($y = 0; ($y < $this->height); $y++)
            {
                for ($x = 0; ($x < $this->width); $x++)
                {
                    $value = &$this->vImage[$y][$x];
                    $value = $value > 1 ? 0 : $value;
                }
            }
        }
    
        private function _drawVirtualLine($x1, $y1, $x2, $y2)
        {
            // Draw a vertial line in the virtual image
            if ($x1 == $x2)
            {
                if ($y1 > $y2)
                {
                    list($x1, $y1, $x2, $y2) = array ($x2, $y2, $x1, $y1);
                }
                for ($y = $y1; ($y <= $y2); $y++)
                {
                    $this->vImage[$y][$x1]++;
                }
            }
    
            // Draw an horizontal line in the virtual image
            if ($y1 == $y2)
            {
                if ($x1 > $x2)
                {
                    list($x1, $y1, $x2, $y2) = array ($x2, $y2, $x1, $y1);
                }
                for ($x = $x1; ($x <= $x2); $x++)
                {
                    $this->vImage[$y1][$x]++;
                }
            }
    
            // Force corners to be 1 (because one corner is at least used 2 times but we don't want to remove them)
            $this->vImage[$y1][$x1] = 1;
            $this->vImage[$y1][$x2] = 1;
            $this->vImage[$y2][$x1] = 1;
            $this->vImage[$y2][$x2] = 1;
        }
    
        private function _findPolygon()
        {
            // We're looking for the first point in the virtual image
            foreach ($this->vImage as $y => $row)
            {
                foreach ($row as $x => $value)
                {
                    if ($value == 1)
                    {
                        // Removes alone points ( every corner have been set to 1, but some corners are alone (eg: middle  of 4 rectangles)
                        if ((!$this->_hasPixelAtBottom($x, $y)) && (!$this->_hasPixelAtTop($x, $y))
                           && (!$this->_hasPixelAtRight($x, $y)) && (!$this->_hasPixelAtLeft($x, $y)))
                        {
                            $this->vImage[$y][$x] = 0;
                            continue;
                        }
                        return array ($x, $y);
                    }
                }
            }
            return null;
        }
    
        private function _hasPixelAtBottom($x, $y)
        {
            // The closest bottom point is a point positionned at (x, y + 1)
            return $this->_hasPixelAt($x, $y + 1);
        }
    
        private function _hasPixelAtTop($x, $y)
        {
            // The closest top point is a point positionned at (x, y - 1)
            return $this->_hasPixelAt($x, $y - 1);
        }
    
        private function _hasPixelAtLeft($x, $y)
        {
            // The closest left point is a point positionned at (x - 1, y)
            return $this->_hasPixelAt($x - 1, $y);
        }
    
        private function _hasPixelAtRight($x, $y)
        {
            // The closest right point is a point positionned at (x + 1, y)
            return $this->_hasPixelAt($x + 1, $y);
        }
    
        private function _hasPixelAt($x, $y)
        {
            // Check if the pixel (x, y) exists
            return ((isset($this->vImage[$y])) && (isset($this->vImage[$y][$x])) && ($this->vImage[$y][$x] > 0));
        }
    
        private function _cleanAndReturnPolygonPoint(array $point)
        {
            // Remove a point from the virtual image
            list($x, $y) = $point;
            $this->vImage[$y][$x] = 0;
            return $point;
        }
    
        private function _getNextPolygonPoint(array $point)
        {
            list($x, $y) = $point;
    
            // Initialize modifiers, to move to the right, bottom, left or top.
            $directions = array(
                    array(1, 0), // right
                    array(0, 1), // bottom
                    array(-1, 0), // left
                    array(0, -1), // top
            );
    
            // Try to get to one direction, if we can go ahead, there is a following corner
            $return = null;
            foreach ($directions as $direction)
            {
                list($xModifier, $yModifier) = $direction;
                if (($return = $this->_iterateDirection($x, $y, $xModifier, $yModifier)) !== null)
                {
                    return $return;
                }
            }
    
            // the point is alone : we are at the end of the polygon
            return $return;
        }
    
        private function _iterateDirection($x, $y, $xModifier, $yModifier)
        {
            // This method follows points in a direction until the last point
            $return = null;
            while ($this->_hasPixelAt($x + $xModifier, $y + $yModifier))
            {
                $x = $x + $xModifier;
                $y = $y + $yModifier;
    
                // Important : we remove the point so we'll not get back when moving
                $return = $this->_cleanAndReturnPolygonPoint(array ($x, $y));
            }
    
            // The last point is a corner of the polygon because if it has no following point, we change direction
            return $return;
        }
    
        /**
         * This method draws a polygon with the given points. That's to check if
         * our calculations are valid.
         *
         * @param array $points An array of points that define the polygon
         */
        public function drawPolygon(array $points, $r, $g, $b)
        {
            $count = count($points);
            for ($i = 0; ($i < $count); $i++)
            {
                // Draws a line between the current and the next point until the last point is reached
                if (array_key_exists($i + 1, $points))
                {
                    list($x1, $y1) = $points[$i];
                    list($x2, $y2) = $points[$i + 1];
                    $black = imagecolorallocate($this->image, $r, $g, $b);
                    imageline($this->image, $x1, $y1, $x2, $y2, $black);
                }
            }
        }
    
    }
    
    $rectanglesA = array (
            array ( // 1
                    array (50, 50), // tx, ty
                    array (75, 75), // bx, by
            ),
            array ( // 2
                    array (75, 50), // tx, ty
                    array (125, 75), // bx, by
            ),
            array ( // 3
                    array (125, 50), // tx, ty
                    array (175, 75), // bx, by
            ),
            array ( // 4
                    array (175, 50), // tx, ty
                    array (225, 75), // bx, by
            ),
            array ( // 5
                    array (225, 50), // tx, ty
                    array (275, 75), // bx, by
            ),
            array ( // 6
                    array (275, 50), // tx, ty
                    array (325, 75), // bx, by
            ),
            array ( // 7
                    array (325, 50), // tx, ty
                    array (375, 75), // bx, by
            ),
            array ( // 8
                    array (375, 50), // tx, ty
                    array (425, 75), // bx, by
            ),
            array ( // 9
                    array (320, 42), // tx, ty
                    array (330, 50), // bx, by
            ),
            array ( // 10
                    array (425, 60), // tx, ty
                    array (430, 65), // bx, by
            ),
            array ( // 11
                    array (100, 75), // tx, ty
                    array (150, 250), // bx, by
            ),
            array ( // 12
                    array (150, 125), // tx, ty
                    array (250, 150), // bx, by
            ),
            array ( // 13
                    array (225, 75), // tx, ty
                    array (250, 125), // bx, by
            ),
            array ( // 14
                    array (150, 92), // tx, ty
                    array (180, 107), // bx, by
            ),
    );
    
    $rectanglesB = array (
            array ( // 15
                    array (200, 300), // tx, ty
                    array (250, 350), // bx, by
            ),
            array ( // 16
                    array (250, 250), // tx, ty
                    array (300, 300), // bx, by
            ),
            array ( // 17
                    array (250, 300), // tx, ty
                    array (300, 350), // bx, by
            ),
            array ( // 18
                    array (300, 250), // tx, ty
                    array (350, 300), // bx, by
            ),
            array ( // 19
                    array (300, 300), // tx, ty
                    array (350, 350), // bx, by
            ),
            array ( // 20
                    array (300, 200), // tx, ty
                    array (350, 250), // bx, by
            ),
            array ( // 21
                    array (350, 300), // tx, ty
                    array (400, 350), // bx, by
            ),
            array ( // 22
                    array (350, 200), // tx, ty
                    array (400, 250), // bx, by
            ),
            array ( // 23
                    array (350, 150), // tx, ty
                    array (400, 200), // bx, by
            ),
            array ( // 24
                    array (400, 200), // tx, ty
                    array (450, 250), // bx, by
            ),
    );
    
    $polygonMaker = new PolygonMaker(500, 400);
    
    // Just to get started and see what's happens
    //$polygonMaker->drawRectangles($rectanglesA, 0xFF, 0x00, 0x00);
    //$polygonMaker->drawRectangles($rectanglesB, 0xFF, 0x00, 0x00);
    
    $polygonsA = $polygonMaker->findPolygonsPoints($rectanglesA);
    foreach ($polygonsA as $polygon)
    {
        $polygonMaker->drawPolygon($polygon, 0x00, 0x00, 0x00);
    }
    
    $polygonsB = $polygonMaker->findPolygonsPoints($rectanglesB);
    foreach ($polygonsB as $polygon)
    {
        $polygonMaker->drawPolygon($polygon, 0x00, 0x00, 0x00);
    }
    
    // Display image to see if everything is correct
    $polygonMaker->display();
    
    <?php 
    
    class RectUnion {
        private $x, $y;
        private $sides;
        private $points;
    
        function __construct() {
            $this->x = array();
            $this->y = array();
            $this->sides = array();
            $this->points = array();
        }
    
        function addRect($r) {
            extract($r);
            $this->x[] = $x1;
            $this->x[] = $x2;
            $this->y[] = $y1;
            $this->y[] = $y2;
            if ($x1 > $x2) { $tmp = $x1; $x1 = $x2; $x2 = $tmp; }
            if ($y1 > $y2) { $tmp = $y1; $y1 = $y2; $y2 = $tmp; }
            $this->sides[] = array($x1, $y1, $x2, $y1);
            $this->sides[] = array($x2, $y1, $x2, $y2);
            $this->sides[] = array($x1, $y2, $x2, $y2);
            $this->sides[] = array($x1, $y1, $x1, $y2);
        }
    
        function splitSides() {
           $result = array();
           $this->x = array_unique($this->x);
           $this->y = array_unique($this->y);
           sort($this->x);
           sort($this->y);
           foreach ($this->sides as $i => $s) {
               if ($s[0] - $s[2]) {     // Horizontal
                   foreach ($this->x as $xx) {
                       if (($xx > $s[0]) && ($xx < $s[2])) {
                           $result[] = array($s[0], $s[1], $xx, $s[3]);
                           $s[0] = $xx;
                       }
                   }
               } else {                 // Vertical
                   foreach ($this->y as $yy) {
                       if (($yy > $s[1]) && ($yy < $s[3])) {
                           $result[] = array($s[0], $s[1], $s[2], $yy);
                           $s[1] = $yy;
                       }
                   }
               }
               $result[] = $s;
           }
           return($result);
        }
    
        function removeDuplicates($sides) {
            $x = array();
            foreach ($sides as $i => $s) {
                @$x[$s[0].','.$s[1].','.$s[2].','.$s[3]]++;
            }
            foreach ($x as $s => $n) {
                if ($n > 1) {
                  unset($x[$s]);
                } else {
                  $this->points[] = explode(",", $s);
                }
            }
            return($x);
        }
    
        function drawPoints($points, $outfile = null) {
            $xs = $this->x[count($this->x) - 1] + $this->x[0];
            $ys = $this->y[count($this->y) - 1] + $this->y[0];
            $img = imagecreate($xs, $ys);
            if ($img !== FALSE) {
                $wht = imagecolorallocate($img, 255, 255, 255);
                $blk = imagecolorallocate($img, 0, 0, 0);
                $red = imagecolorallocate($img, 255, 0, 0);
                imagerectangle($img, 0, 0, $xs - 1, $ys - 1, $red);
                $oldp = $points[0];
                for ($i = 1; $i < count($points); $i++) {
                    $p = $points[$i];
                    imageline($img, $oldp['x'], $oldp['y'], $p['x'], $p['y'], $blk);
                    $oldp = $p;
                }
                imageline($img, $oldp['x'], $oldp['y'], $points[0]['x'], $points[0]['y'], $blk);
                if ($outfile == null) header("content-type: image/png");
                imagepng($img, $outfile);
                imagedestroy($img);
            }
        }
    
        function drawSides($sides, $outfile = null) {
            $xs = $this->x[count($this->x) - 1] + $this->x[0];
            $ys = $this->y[count($this->y) - 1] + $this->y[0];
            $img = imagecreate($xs, $ys);
            if ($img !== FALSE) {
                $wht = imagecolorallocate($img, 255, 255, 255);
                $blk = imagecolorallocate($img, 0, 0, 0);
                $red = imagecolorallocate($img, 255, 0, 0);
                imagerectangle($img, 0, 0, $xs - 1, $ys - 1, $red);
                foreach ($sides as $s => $n) {
                    if (is_array($n)) {
                        $r = $n;
                    } else {
                        $r = explode(",", $s);
                    }
                    imageline($img, $r['x1'], $r['y1'], $r['x2'], $r['y2'], $blk);
                }
                if ($outfile == null) header("content-type: image/png");
                imagepng($img, $outfile);
                imagedestroy($img);
            }
        }
    
        function getSides($sides = FALSE) {
            if ($sides === FALSE) {
                foreach ($this->sides as $r) {
                    $result[] = array('x1' => $r[0], 'y1' => $r[1], 'x2' => $r[2], 'y2' => $r[3]);
                }
            } else {
                $result = array();
                foreach ($sides as $s => $n) {
                    $r = explode(",", $s);
                    $result[] = array('x1' => $r[0], 'y1' => $r[1], 'x2' => $r[2], 'y2' => $r[3]);
                }
            }
            return($result);
        }
    
        private function _nextPoint(&$points, $lastpt) {
            @extract($lastpt);
            foreach ($points as $i => $p) {
                if (($p[0] == $x) && ($p[1] == $y)) {
                    unset($points[$i]);
                    return(array('x' => $p[2], 'y' => $p[3]));
                } else if (($p[2] == $x) && ($p[3] == $y)) {
                    unset($points[$i]);
                    return(array('x' => $p[0], 'y' => $p[1]));
                }
            }
            return false;
        }
    
        function getPoints($points = FALSE) {
            if ($points === FALSE) $points = $this->points;
            $result = array(
                array('x' => $points[0][0], 'y' => $points[0][1])
            );
            $lastpt = array('x' => $points[0][2], 'y' => $points[0][3]);
            unset($points[0]);
            do {
                $result[] = $lastpt;
            } while ($lastpt = $this->_nextPoint($points, $lastpt));
            return($result);
        }
    }
    
    ?>
    
    <?php
    
    require_once("RectUnion.php");
    
    function generateRect($prev, $step) {
        $rect = array(
            'x1' => $prev['x2'],
            'x2' => $prev['x2'] + rand($step, $step * 10),
            'y1' => rand($prev['y1'] + 2, $prev['y2'] - 2),
            'y2' => rand($step * 2, $step * 10)
        );
        return($rect);
    }
    
    $x0 = 50;       // Pixels
    $y0 = 50;       // Pixels
    $step = 20;     // Pixels
    $nrect = 10;    // Number of rectangles
    $rects = array(
        array("x1" => 50, "y1" => 50, "x2" => 100, "y2" => 100)
    );
    for ($i = 1; $i < $nrect - 1; $i++) {
        $rects[$i] = generateRect($rects[$i - 1], $step);
    }
    
    $start_tm = microtime(true);
    
    $ru = new RectUnion();
    foreach ($rects as $r) {
        $ru->addRect($r);
    }
    $union = $ru->removeDuplicates($ru->splitSides());
    
    $stop_tm = microtime(true);
    
    $ru->drawSides($ru->getSides(), "before.png");
    
    if (FALSE) {    // Lines
        $sides = $ru->getSides($union);
        $ru->drawSides($sides, "after.png");
    } else {        // Points
        $points = $ru->getPoints();
        $ru->drawPoints($points, "after.png");
    }
    
    ?>
    <!DOCTYPE html>
    <html>
        <body>
            <fieldset>
                <legend>Before Union</legend>
                <img src='before.png'>
            </fieldset>
            <fieldset>
                <legend>After Union</legend>
                <img src='after.png'>
            </fieldset>
            <h4>Elapsed Time: <?= round($stop_tm - $start_tm, 4) ?> seconds</h4>
            <?php if (isset($sides)): ?>
            <h4>Sides:</h4>
            <pre><?= print_r($sides, true) ?></pre>
            <?php elseif (isset($points)): ?>
            <h4>Points:</h4>
            <pre><?= print_r($points, true) ?></pre>
            <?php endif ?>
        </body>
    </html>
    
    # These rectangles resemble the OP's illustration.
    rect = ([[0,  10], [10, 0]],
            [[10, 13], [19, 0]],
            [[19, 10], [23, 0]])
    
    points = set()
    for (x1, y1), (x2, y2) in rect:
        for pt in ((x1, y1), (x2, y1), (x2, y2), (x1, y2)):
            if pt in points: # Shared vertice, remove it.
                points.remove(pt)
            else:
                points.add(pt)
    points = list(points)
    
    def y_then_x(a, b):
        if a[1] < b[1] or (a[1] == b[1] and a[0] < b[0]):
            return -1
        elif a == b:
            return 0
        else:
            return 1
    
    sort_x = sorted(points)
    sort_y = sorted(points, cmp=y_then_x)
    
    edges_h = {}
    edges_v = {}
    
    i = 0
    while i < len(points):
        curr_y = sort_y[i][1]
        while i < len(points) and sort_y[i][1] == curr_y: //6chars comments, remove it
            edges_h[sort_y[i]] = sort_y[i + 1]
            edges_h[sort_y[i + 1]] = sort_y[i]
            i += 2
    i = 0
    while i < len(points):
        curr_x = sort_x[i][0]
        while i < len(points) and sort_x[i][0] == curr_x:
            edges_v[sort_x[i]] = sort_x[i + 1]
            edges_v[sort_x[i + 1]] = sort_x[i]
            i += 2
    
    # Get all the polygons.
    p = []
    while edges_h:
        # We can start with any point.
        polygon = [(edges_h.popitem()[0], 0)]
        while True:
            curr, e = polygon[-1]
            if e == 0:
                next_vertex = edges_v.pop(curr)
                polygon.append((next_vertex, 1))
            else:
                next_vertex = edges_h.pop(curr)
                polygon.append((next_vertex, 0))
            if polygon[-1] == polygon[0]:
                # Closed polygon
                polygon.pop()
                break
        # Remove implementation-markers from the polygon.
        poly = [point for point, _ in polygon]
        for vertex in poly:
            if vertex in edges_h: edges_h.pop(vertex)
            if vertex in edges_v: edges_v.pop(vertex)
    
        p.append(poly)
    
    
    for poly in p:
        print poly
    
    [(0, 0), (0, 10), (10, 10), (10, 13), (19, 13), (19, 10), (23, 10), (23, 0)]
    
    rect = ([[1, 2], [3, 1]], [[1, 4], [2, 2]], [[1, 6], [2, 4]], [[2, 6], [3, 5]],
            [[3, 8], [4, 4]], [[2, 8], [3, 7]], [[3, 10], [5, 8]], [[3, 4], [9, 3]],
            [[4, 5], [7, 4]], [[6, 8], [7, 5]], [[6, 9], [8, 8]], [[8, 9], [10, 6]],
            [[9, 6], [10, 3]])
    
    [(6, 9), (6, 5), (4, 5), (4, 8), (5, 8), (5, 10), (3, 10), (3, 8),
     (2, 8), (2, 7), (3, 7), (3, 6), (1, 6), (1, 1), (3, 1), (3, 2),
     (2, 2), (2, 5), (3, 5), (3, 3), (10, 3), (10, 9)]
    
    [(9, 4), (9, 6), (8, 6), (8, 8), (7, 8), (7, 4)]
    
    const pointsToPolygons = (points, size) => {
        let edges_v = {}, edges_h = {};
        const setEdges = (edges, cmp, e) => {
            points.sort(cmp);
            let edge_index = 0;
            const length = points.length;
            while(edge_index < length) {
                const curr = points[edge_index][e];
                do {
                    edges[points[edge_index]] = points[edge_index+1];
                    edges[points[edge_index+1]] = points[edge_index];
                    edge_index += 2
                } while(edge_index < length && points[edge_index][e] == curr);
            }
        };
        setEdges(edges_v, xThenY, 0);
        setEdges(edges_h, yThenX, 1);
        
        let polygon = [], keys;
        while((keys = Object.keys(edges_h)).length) {
            const [ key ] = keys;
            delete edges_h[key];
            
            const first_vertex = new V2(key);
            let previous = [first_vertex.toArray(), 0];
            let vertices = [first_vertex];
    
            while(1) {
                const [edge_index, edge] = previous;
                const edges = [edges_v, edges_h][edge];
                const next_vertex = new V2(edges[edge_index]);
                const next = [next_vertex.toArray(), 1-edge];
                delete edges[edge_index];
    
                if(first_vertex.compare(next_vertex)) {
                    break;
                }
    
                vertices.push(next_vertex);
                previous = next;
            }
    
            let scaled_vertices = [];
            for(let vertex of vertices) {
                scaled_vertices.push(vertex.scale(size).toArray());
    
                const edge_index = vertex.toArray();
                delete edges_v[edge_index];
                delete edges_h[edge_index];
            }
    
            polygon.push(scaled_vertices);
        }
    
        return polygon;
    };
    
    function V2(x,y) {
        if(Array.isArray(x)) {
            y = x[1];
            x = x[0];
        } else {
            switch(typeof x) {
                case 'object': 
                    y = x.y;
                    x = x.x;
                break;
    
                case 'string':
                    const split = x.split(',');
                    x = parseInt(split[0]);
                    y = parseInt(split[1]);
                break;
            }
        }
    
        this.x = x;
        this.y = y;
    }
    
    V2.prototype = {
        scale: function(scale) {
            return new V2(this.x * scale, this.y * scale);
        },
        compare: function(v) {
            return this.x == v.x && this.y == v.y;
        },
        toArray: function() {
            return [this.x, this.y];
        }
    };
    
    const xThenY = (a,b) => a[0]<b[0] || (a[0]==b[0] && a[1]<b[1]) ? -1 : 1;
    const yThenX = (a,b) => a[1]<b[1] || (a[1]==b[1] && a[0]<b[0]) ? -1 : 1;