• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

brick / geo / 17456208570

04 Sep 2025 07:10AM UTC coverage: 50.432%. Remained the same
17456208570

push

github

BenMorel
Use @extends and @implements instead of @template-* variants

For consistency with the rest of the project.

1867 of 3702 relevant lines covered (50.43%)

1140.21 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

54.76
/src/PolyhedralSurface.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Brick\Geo;
6

7
use ArrayIterator;
8
use Brick\Geo\Attribute\NoProxy;
9
use Brick\Geo\Exception\CoordinateSystemException;
10
use Brick\Geo\Exception\NoSuchGeometryException;
11
use Brick\Geo\Exception\UnexpectedGeometryException;
12
use Brick\Geo\Projector\Projector;
13
use Countable;
14
use IteratorAggregate;
15
use Override;
16

17
use function array_map;
18
use function array_reduce;
19
use function array_values;
20
use function count;
21
use function sprintf;
22

23
/**
24
 * A PolyhedralSurface is a contiguous collection of polygons, which share common boundary segments.
25
 *
26
 * For each pair of polygons that "touch", the common boundary shall be expressible as a finite collection
27
 * of LineStrings. Each such LineString shall be part of the boundary of at most 2 Polygon patches.
28
 *
29
 * For any two polygons that share a common boundary, the "top" of the polygon shall be consistent. This means
30
 * that when two linear rings from these two Polygons traverse the common boundary segment, they do so in
31
 * opposite directions. Since the Polyhedral surface is contiguous, all polygons will be thus consistently oriented.
32
 * This means that a non-oriented surface (such as Möbius band) shall not have single surface representations.
33
 * They may be represented by a MultiSurface.
34
 *
35
 * If each such LineString is the boundary of exactly 2 Polygon patches, then the PolyhedralSurface is a simple,
36
 * closed polyhedron and is topologically isomorphic to the surface of a sphere. By the Jordan Surface Theorem
37
 * (Jordan’s Theorem for 2-spheres), such polyhedrons enclose a solid topologically isomorphic to the interior of a
38
 * sphere; the ball. In this case, the "top" of the surface will either point inward or outward of the enclosed
39
 * finite solid. If outward, the surface is the exterior boundary of the enclosed surface. If inward, the surface
40
 * is the interior of the infinite complement of the enclosed solid. A Ball with some number of voids (holes) inside
41
 * can thus be presented as one exterior boundary shell, and some number in interior boundary shells.
42
 *
43
 * @template T of Polygon
44
 *
45
 * @implements IteratorAggregate<int<0, max>, T>
46
 */
47
class PolyhedralSurface extends Surface implements Countable, IteratorAggregate
48
{
49
    /**
50
     * The polygons that compose this PolyhedralSurface.
51
     *
52
     * An empty PolyhedralSurface contains no polygons.
53
     *
54
     * @var list<T>
55
     */
56
    protected array $patches = [];
57

58
    /**
59
     * The coordinate system of each of the patches must match the one of the PolyhedralSurface.
60
     *
61
     * @param CoordinateSystem $cs         The coordinate system of the PolyhedralSurface.
62
     * @param T                ...$patches The patches that compose the PolyhedralSurface.
63
     *
64
     * @throws CoordinateSystemException If different coordinate systems are used.
65
     */
66
    public function __construct(CoordinateSystem $cs, Polygon ...$patches)
67
    {
68
        parent::__construct($cs, ! $patches);
3,280✔
69

70
        if (! $patches) {
3,280✔
71
            return;
1,584✔
72
        }
73

74
        CoordinateSystem::check($this, ...$patches);
1,728✔
75

76
        $patchType = $this->patchType();
1,728✔
77

78
        foreach ($patches as $patch) {
1,728✔
79
            /**
80
             * @psalm-suppress DocblockTypeContradiction We do want to enforce this in code, as not everyone uses static analysis!
81
             * @psalm-suppress MixedArgument It looks like due to this check, Psalm considers that $geometry no longer has a type.
82
             */
83
            if (! $patch instanceof $patchType) {
1,728✔
84
                throw new UnexpectedGeometryException(sprintf(
×
85
                    '%s expects instance of %s, instance of %s given.',
×
86
                    static::class,
×
87
                    $patchType,
×
88
                    $patch::class,
×
89
                ));
×
90
            }
91
        }
92

93
        $this->patches = array_values($patches);
1,728✔
94
    }
95

96
    /**
97
     * Creates a non-empty PolyhedralSurface composed of the given patches.
98
     *
99
     * @param Polygon $patch1    The first patch.
100
     * @param Polygon ...$patchN The subsequent patches, if any.
101
     *
102
     * @throws CoordinateSystemException If the patches use different coordinate systems.
103
     *
104
     * @psalm-suppress UnsafeInstantiation
105
     */
106
    public static function of(Polygon $patch1, Polygon ...$patchN): PolyhedralSurface
107
    {
108
        return new static($patch1->coordinateSystem(), $patch1, ...$patchN);
8✔
109
    }
110

111
    public function numPatches(): int
112
    {
113
        return count($this->patches);
64✔
114
    }
115

116
    /**
117
     * Returns the specified patch N in this PolyhedralSurface.
118
     *
119
     * @param int $n The patch number, 1-based.
120
     *
121
     * @return T
122
     *
123
     * @throws NoSuchGeometryException If there is no patch at this index.
124
     */
125
    public function patchN(int $n): Polygon
126
    {
127
        if (! isset($this->patches[$n - 1])) {
392✔
128
            throw new NoSuchGeometryException('There is no patch in this PolyhedralSurface at index ' . $n);
256✔
129
        }
130

131
        return $this->patches[$n - 1];
136✔
132
    }
133

134
    /**
135
     * Returns the patches that compose this PolyhedralSurface.
136
     *
137
     * @return list<T>
138
     */
139
    public function patches(): array
140
    {
141
        return $this->patches;
×
142
    }
143

144
    #[NoProxy, Override]
145
    public function geometryType(): string
146
    {
147
        return 'PolyhedralSurface';
784✔
148
    }
149

150
    #[NoProxy, Override]
151
    public function geometryTypeBinary(): int
152
    {
153
        return Geometry::POLYHEDRALSURFACE;
392✔
154
    }
155

156
    #[Override]
157
    public function getBoundingBox(): BoundingBox
158
    {
159
        return array_reduce(
×
160
            $this->patches,
×
161
            fn (BoundingBox $boundingBox, Polygon $patch) => $boundingBox->extendedWithBoundingBox($patch->getBoundingBox()),
×
162
            BoundingBox::new(),
×
163
        );
×
164
    }
165

166
    /**
167
     * @return list<list<list<list<float>>>>
168
     */
169
    #[Override]
170
    public function toArray(): array
171
    {
172
        return array_map(
512✔
173
            fn (Polygon $patch) => $patch->toArray(),
512✔
174
            $this->patches,
512✔
175
        );
512✔
176
    }
177

178
    #[Override]
179
    public function project(Projector $projector): PolyhedralSurface
180
    {
181
        return new PolyhedralSurface(
×
182
            $projector->getTargetCoordinateSystem($this->coordinateSystem),
×
183
            ...array_map(
×
184
                fn (Polygon $patch) => $patch->project($projector),
×
185
                $this->patches,
×
186
            ),
×
187
        );
×
188
    }
189

190
    /**
191
     * Returns the number of patches in this PolyhedralSurface.
192
     */
193
    #[Override]
194
    public function count(): int
195
    {
196
        return count($this->patches);
784✔
197
    }
198

199
    /**
200
     * Returns an iterator for the patches in this PolyhedralSurface.
201
     *
202
     * @return ArrayIterator<int<0, max>, T>
203
     */
204
    #[Override]
205
    public function getIterator(): ArrayIterator
206
    {
207
        return new ArrayIterator($this->patches);
1,576✔
208
    }
209

210
    /**
211
     * Returns a copy of this PolyhedralSurface, with the given patches added.
212
     *
213
     * @psalm-suppress UnsafeInstantiation
214
     */
215
    public function withAddedPatches(Polygon ...$patches): PolyhedralSurface
216
    {
217
        return new static($this->coordinateSystem, ...$this->patches, ...$patches);
120✔
218
    }
219

220
    /**
221
     * Returns the FQCN of the contained patch type.
222
     *
223
     * @return class-string<T>
224
     */
225
    protected function patchType(): string
226
    {
227
        return Polygon::class;
1,024✔
228
    }
229
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc