Merging multiple adjacent rectangles into one polygon

前端 未结 4 2015
挽巷
挽巷 2020-12-08 05:24

Background: I am working on a site for small shopping center, which has multiple rectangular \"units\" to rent. When a \"shop\" comes, it can rent one or mu

4条回答
  •  臣服心动
    2020-12-08 06:12

    Here is my solution:

    RectUnion.php

    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);
        }
    }
    
    ?>
    

    merge.php

     $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");
    }
    
    ?>
    
    
        
            
    Before Union
    After Union

    Elapsed Time: seconds

    Sides:

    Points:

    How does it work?

    The script identifies and removes all "overlapping" segments. For example:
    example

    First, the sides of each rectangle are split at the intersections with the sides of the adiacent rectangle.
    For example, consider the B2-B3 side of the B rectangle: the "splitSides" method splits it into the B2-D1, D1-D4 and D4-B3 segments.
    Then the "removeDuplicates" method removes all the overlapping (duplicate) segments.
    For example, the D1-D4 segment is a duplicate, since it appears either in the B rectangle and in the D rectangle.
    Finally the "getSides" method returns the list of the remaining segments, while the "getPoints" method returns the list of the polygon points.
    The "draw" methods are only for the graphical representation of the result, and require the GD extension to work:
    result

    About performance

    Here are some execution times:

    • 10 rectangles: 0,003 seconds
    • 100 rectangles: 0,220 seconds
    • 1000 rectangles: 4,407 seconds
    • 2000 rectangles: 13,448 seconds

    By profiling the execution with XDebug, I've got the following results:

    cachegrind

提交回复
热议问题