5G-LENA nr-v3.1-69-g2dd513a7
The 5G/NR module for the ns-3 simulator
Loading...
Searching...
No Matches
hexagonal-grid-scenario-helper.cc
1// Copyright (c) 2020 Centre Tecnologic de Telecomunicacions de Catalunya (CTTC)
2//
3// SPDX-License-Identifier: GPL-2.0-only
4
5#include "hexagonal-grid-scenario-helper.h"
6
7#include "ns3/constant-velocity-mobility-model.h"
8#include <ns3/double.h>
9#include <ns3/mobility-helper.h>
10
11#include <cmath>
12
13namespace ns3
14{
15
16void
17HexagonalGridScenarioHelper::SetResultsDir(std::string resultsDir)
18{
19 m_resultsDir = resultsDir;
20}
21
22void
23HexagonalGridScenarioHelper::SetSimTag(std::string simTag)
24{
25 m_simTag = simTag;
26}
27
29{
30 m_r = CreateObject<UniformRandomVariable>();
31 m_theta = CreateObject<UniformRandomVariable>();
32}
33
37
38const double distTo2ndRing = std::sqrt(3);
39const double distTo4thRing = std::sqrt(7);
40// Site positions in terms of distance and angle w.r.t. the central site
41std::vector<double> HexagonalGridScenarioHelper::siteDistances{0,
42 1,
43 1,
44 1,
45 1,
46 1,
47 1,
48 distTo2ndRing,
49 distTo2ndRing,
50 distTo2ndRing,
51 distTo2ndRing,
52 distTo2ndRing,
53 distTo2ndRing,
54 2,
55 2,
56 2,
57 2,
58 2,
59 2,
60 distTo4thRing,
61 distTo4thRing,
62 distTo4thRing,
63 distTo4thRing,
64 distTo4thRing,
65 distTo4thRing,
66 distTo4thRing,
67 distTo4thRing,
68 distTo4thRing,
69 distTo4thRing,
70 distTo4thRing,
71 distTo4thRing,
72 3,
73 3,
74 3,
75 3,
76 3,
77 3};
78
79/*
80 * Site angles w.r.t. the central site center.
81 *
82 * Note that the angles in the following vector are when looking the deployment in which hexagons
83 * are oriented in the following way:
84 *
85 * ^ ______
86 * | / \
87 * | ______/ \
88 * | / \ /
89 * y | / \______/
90 * | \ / \
91 * | \______/ \
92 * | \ /
93 * | \______/
94 * ------------------------>
95 * x
96 *
97 * This is important to note because the gnuplot function of this the HexagonalGridScenarioHelper
98 * plots hexagon in different orientation pointing towards top-bottom, e.g.:
99 *
100 * /\
101 * / \
102 * | |
103 * | |
104 * \ /
105 * \/
106 *
107 */
108
109// the angle of the first hexagon of the fourth ring in the first quadrant
110const double ang4thRingAlpha1 = atan2(1, (3 * sqrt(3))) * (180 / M_PI);
111// the angle of the second hexagon of the fourth ring in the first quadrant
112const double ang4thRingAlpha2 = 90 - atan2(sqrt(3), 2) * (180 / M_PI);
113// the angle of the third hexagon of the fourth ring in the first quadrant
114const double ang4thRingAlpha3 = 90 - atan2(3, (5 * sqrt(3))) * (180 / M_PI);
115
116std::vector<double> HexagonalGridScenarioHelper::siteAngles{
117 0, // 0 ring
118 30,
119 90,
120 150,
121 210,
122 270,
123 330, // 1. ring
124 0,
125 60,
126 120,
127 180,
128 240,
129 300, // 2. ring
130 30,
131 90,
132 150,
133 210,
134 270,
135 330, // 3. ring
136 ang4thRingAlpha1,
137 ang4thRingAlpha2,
138 ang4thRingAlpha3, // 4. ring 1. quadrant
139 180 - ang4thRingAlpha3,
140 180 - ang4thRingAlpha2,
141 180 - ang4thRingAlpha1, // 4. ring 2. quadrant
142 180 + ang4thRingAlpha1,
143 180 + ang4thRingAlpha2,
144 180 + ang4thRingAlpha3, // 4. ring 3. quadrant
145 -ang4thRingAlpha3,
146 -ang4thRingAlpha2,
147 -ang4thRingAlpha1, // 4. ring 4. quadrant
148 30,
149 90,
150 150,
151 210,
152 270,
153 330 // 5. ring
154};
155
166static void
167PlotHexagonalDeployment(const Ptr<const ListPositionAllocator>& sitePosVector,
168 const Ptr<const ListPositionAllocator>& cellCenterVector,
169 const Ptr<const ListPositionAllocator>& utPosVector,
170 double cellRadius,
171 std::string resultsDir,
172 std::string simTag)
173{
174 uint16_t numCells = cellCenterVector->GetSize();
175 uint16_t numSites = sitePosVector->GetSize();
176 uint16_t numSectors = numCells / numSites;
177 uint16_t numUts = utPosVector->GetSize();
178 NS_ASSERT_MSG(numCells > 0, "no cells");
179 NS_ASSERT_MSG(numSites > 0, "no sites");
180 NS_ASSERT_MSG(numUts > 0, "no uts");
181
182 // Try to open a new GNUPLOT file
183 std::ofstream topologyOutfile;
184 std::string topologyFileRoot = resultsDir + "./hexagonal-topology";
185 std::string topologyFileName = topologyFileRoot + simTag + ".gnuplot";
186 topologyOutfile.open(topologyFileName.c_str(), std::ios_base::out | std::ios_base::trunc);
187 if (!topologyOutfile.is_open())
188 {
189 NS_ABORT_MSG("Can't open " << topologyFileName);
190 }
191
192 topologyOutfile << "set term pdf" << std::endl;
193 topologyOutfile << "set output \"" << topologyFileName << ".pdf\"" << std::endl;
194 topologyOutfile << "set style arrow 1 lc \"black\" lt 1 head filled" << std::endl;
195 // topologyOutfile << "set autoscale" << std::endl;
196
197 uint16_t margin =
198 (12 * cellRadius) + 1;
199 topologyOutfile << "set xrange [-" << margin << ":" << margin << "]" << std::endl;
200 topologyOutfile << "set yrange [-" << margin << ":" << margin << "]" << std::endl;
201 // FIXME: Need to recalculate ranges if the scenario origin is different to (0,0)
202
203 double arrowLength =
204 cellRadius /
205 4.0; //<! Control the arrow length that indicates the orientation of the sectorized antenna
206 std::vector<double> hx{0.0, -0.5, -0.5, 0.0, 0.5, 0.5, 0.0}; //<! Hexagon vertices in x-axis
207 std::vector<double> hy{-1.0, -0.5, 0.5, 1.0, 0.5, -0.5, -1.0}; //<! Hexagon vertices in y-axis
208 Vector sitePos;
209
210 for (uint16_t cellId = 0; cellId < numCells; ++cellId)
211 {
212 Vector cellPos = cellCenterVector->GetNext();
213 double angleDeg = 30 + 120 * (cellId % 3);
214 double angleRad = angleDeg * M_PI / 180;
215 double x;
216 double y;
217
218 if (cellId % numSectors == 0)
219 {
220 sitePos = sitePosVector->GetNext();
221 }
222 topologyOutfile << "set arrow " << cellId + 1 << " from " << sitePos.x << "," << sitePos.y
223 << " rto " << arrowLength * std::cos(angleRad) << ","
224 << arrowLength * std::sin(angleRad) << " arrowstyle 1 \n";
225
226 // Draw the hexagon arond the cell center
227 topologyOutfile << "set object " << cellId + 1 << " polygon from \\\n";
228
229 for (uint16_t vertexId = 0; vertexId <= 6; ++vertexId)
230 {
231 // angle of the vertex w.r.t. y-axis
232 x = cellRadius * std::sqrt(3.0) * hx.at(vertexId) + cellPos.x;
233 y = cellRadius * hy.at(vertexId) + cellPos.y;
234 topologyOutfile << x << ", " << y;
235 if (vertexId == 6)
236 {
237 topologyOutfile << " front fs empty \n";
238 }
239 else
240 {
241 topologyOutfile << " to \\\n";
242 }
243 }
244
245 topologyOutfile << "set label " << cellId + 1 << " \"" << (cellId + 1) << "\" at "
246 << cellPos.x << " , " << cellPos.y << " center" << std::endl;
247 }
248
249 for (uint16_t utId = 0; utId < numUts; ++utId)
250 {
251 Vector utPos = utPosVector->GetNext();
252 // set label at xPos, yPos, zPos "" point pointtype 7 pointsize 2
253 topologyOutfile << "set label at " << utPos.x << " , " << utPos.y
254 << " point pointtype 7 pointsize 0.2 center" << std::endl;
255 }
256
257 topologyOutfile << "unset key" << std::endl;
258 topologyOutfile << "plot 1/0" << std::endl;
259}
260
261static Vector
262GetClosestSitePosition(Vector cellCenterPos, const Ptr<ListPositionAllocator>& sitePosVector)
263{
264 double minDistance = UINT32_MAX;
265 Vector closestSitePosition;
266
267 for (uint32_t i = 0; i < sitePosVector->GetSize(); i++)
268 {
269 Vector sitePos = sitePosVector->GetNext();
270 double d_x = sitePos.x - cellCenterPos.x;
271 double d_y = sitePos.y - cellCenterPos.y;
272 double distance2D = sqrt(d_x * d_x + d_y * d_y);
273 if (distance2D < minDistance)
274 {
275 minDistance = distance2D;
276 closestSitePosition = sitePos;
277 }
278 }
279
280 NS_ABORT_MSG_IF(minDistance == UINT32_MAX,
281 "Get closest site position function not executed properly.");
282 return closestSitePosition;
283}
284
285void
287{
288 NS_ABORT_MSG_IF(numRings > 5, "Unsupported number of outer rings (Maximum is 5");
289
290 m_numRings = numRings;
291
292 /*
293 * 0 rings = 1 + 6 * 0 = 1 site
294 * 1 rings = 1 + 6 * 1 = 7 sites
295 * 2 rings = 1 + 6 * 2 = 13 sites
296 * 3 rings = 1 + 6 * 3 = 19 site5
297 * 4 rings = 1 + 6 * 5 = 31 sites
298 * 5 rings = 1 + 6 * 6 = 37 sites
299 */
300 switch (numRings)
301 {
302 case 0:
303 m_numSites = 1;
304 break;
305 case 1:
306 m_numSites = 7;
307 break;
308 case 2:
309 m_numSites = 13;
310 break;
311 case 3:
312 m_numSites = 19;
313 break;
314 case 4:
315 m_numSites = 31;
316 break;
317 case 5:
318 m_numSites = 37;
319 break;
320 }
322}
323
324double
326{
327 return m_hexagonalRadius;
328}
329
330Vector
331HexagonalGridScenarioHelper::GetHexagonalCellCenter(const Vector& sitePos, uint16_t cellId) const
332{
333 Vector center(sitePos);
334
335 auto sectors = GetNumSectorsPerSite();
336 switch (sectors)
337 {
338 case 0:
339 NS_ABORT_MSG("Number of sectors has not been defined");
340 break;
341
342 case 1:
343 break;
344
345 case 3:
346 switch (GetSectorIndex(cellId))
347 {
348 case 0:
349 center.x += m_hexagonalRadius * std::sqrt(0.75);
350 center.y += m_hexagonalRadius / 2;
351 break;
352
353 case 1:
354 center.x -= m_hexagonalRadius * std::sqrt(0.75);
355 center.y += m_hexagonalRadius / 2;
356 break;
357
358 case 2:
359 center.y -= m_hexagonalRadius;
360 break;
361
362 default:
363 NS_ABORT_MSG("Unknown sector number: " << GetSectorIndex(cellId));
364 }
365 break;
366
367 default:
368 NS_ABORT_MSG("Unsupported number of sectors");
369 break;
370 }
371
372 return center;
373}
374
375void
377{
378 m_hexagonalRadius = m_isd / 3;
379
380 m_bs.Create(m_numBs);
381 m_ut.Create(m_numUt);
382
383 NS_ASSERT(m_isd > 0);
384 NS_ASSERT(m_numRings < 6);
385 NS_ASSERT(m_hexagonalRadius > 0);
386 NS_ASSERT(m_bsHeight >= 0.0);
387 NS_ASSERT(m_utHeight >= 0.0);
388 NS_ASSERT(m_bs.GetN() > 0);
389 NS_ASSERT(m_ut.GetN() > 0);
390
391 MobilityHelper mobility;
392 Ptr<ListPositionAllocator> bsPosVector = CreateObject<ListPositionAllocator>();
393 Ptr<ListPositionAllocator> bsCenterVector = CreateObject<ListPositionAllocator>();
394 Ptr<ListPositionAllocator> sitePosVector = CreateObject<ListPositionAllocator>();
395 Ptr<ListPositionAllocator> utPosVector = CreateObject<ListPositionAllocator>();
396
397 // BS position
398 for (std::size_t cellId = 0; cellId < m_numBs; cellId++)
399 {
400 uint16_t siteIndex = GetSiteIndex(cellId);
401 Vector sitePos(m_centralPos);
402 const double dist = siteDistances.at(siteIndex);
403 const double angleRad = siteAngles.at(siteIndex) * M_PI / 180;
404 sitePos.x += m_isd * dist * cos(angleRad);
405 sitePos.y += m_isd * dist * sin(angleRad);
406 sitePos.z = m_bsHeight;
407
408 if (GetSectorIndex(cellId) == 0)
409 {
410 sitePosVector->Add(sitePos);
411 }
412
413 // FIXME: Until sites can have more than one antenna array, it is necessary to apply some
414 // distance offset from the site center (gNBs cannot have the same location)
415 Vector bsPos = GetAntennaPosition(sitePos, cellId);
416
417 bsPosVector->Add(bsPos);
418
419 // Store cell center position for plotting the deployment
420 Vector cellCenterPos = GetHexagonalCellCenter(bsPos, cellId);
421 bsCenterVector->Add(cellCenterPos);
422
423 // What about the antenna orientation? It should be dealt with when installing the gNB
424 }
425
426 // To allocate UEs, I need the center of the hexagonal cell.
427 // Allocate UE around the disk of radius isd/3, the diameter of a the
428 // hexagon representing the footprint of a single sector.
429 // Reduce this radius by the min BS-UT distance, to respect that standoff
430 // at the one corner of the sector hexagon where the sector antenna lies.
431 // This results in UTs uniformly distributed in a disc centered on
432 // the sector hexagon; there are no UTs near the vertices of the hexagon.
433 // Spread UEs inside the inner hexagonal radius
434 // Need to weight r to get uniform in the sector hexagon
435 // See https://stackoverflow.com/questions/5837572
436 // Set max = radius^2 here, then take sqrt below
437 const double outerR = (std::sqrt(3) / 2) * m_hexagonalRadius - m_minBsUtDistance;
438 m_r->SetAttribute("Min", DoubleValue(0));
439 m_r->SetAttribute("Max", DoubleValue(outerR * outerR));
440 m_theta->SetAttribute("Min", DoubleValue(-1.0 * M_PI));
441 m_theta->SetAttribute("Max", DoubleValue(M_PI));
442
443 // UT position
444
445 for (uint32_t utId = 0; utId < m_ut.GetN(); ++utId)
446 {
447 double d = std::sqrt(m_r->GetValue());
448 double t = m_theta->GetValue();
449
450 // Vector utPos (cellCenterPos);
451 Vector utPos(bsCenterVector->GetNext());
452 utPos.x += d * cos(t);
453 utPos.y += d * sin(t);
454 utPos.z = m_utHeight;
455
456 utPosVector->Add(utPos);
457 }
458
459 mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
460 mobility.SetPositionAllocator(bsPosVector);
461 mobility.Install(m_bs);
462
463 mobility.SetPositionAllocator(utPosVector);
464 mobility.Install(m_ut);
465
466 PlotHexagonalDeployment(sitePosVector,
467 bsCenterVector,
468 utPosVector,
469 m_hexagonalRadius,
470 m_resultsDir,
471 m_simTag);
472}
473
474void
475HexagonalGridScenarioHelper::CreateScenarioWithMobility(const Vector& speed, double percentage)
476{
477 m_hexagonalRadius = m_isd / 3;
478
479 m_bs.Create(m_numBs);
480 m_ut.Create(m_numUt);
481
482 NS_ASSERT(m_isd > 0);
483 NS_ASSERT(m_numRings < 6);
484 NS_ASSERT(m_hexagonalRadius > 0);
485 NS_ASSERT(m_bsHeight >= 0.0);
486 NS_ASSERT(m_utHeight >= 0.0);
487 NS_ASSERT(m_bs.GetN() > 0);
488 NS_ASSERT(m_ut.GetN() > 0);
489 NS_ASSERT_MSG(percentage >= 0 || percentage <= 1,
490 "Percentage must between 0"
491 " and 1");
492
493 MobilityHelper mobility;
494 MobilityHelper ueMobility;
495 Ptr<ListPositionAllocator> bsPosVector = CreateObject<ListPositionAllocator>();
496 Ptr<ListPositionAllocator> bsCenterVector = CreateObject<ListPositionAllocator>();
497 Ptr<ListPositionAllocator> sitePosVector = CreateObject<ListPositionAllocator>();
498 Ptr<ListPositionAllocator> utPosVector = CreateObject<ListPositionAllocator>();
499
500 // BS position
501 for (std::size_t cellId = 0; cellId < m_numBs; cellId++)
502 {
503 uint16_t siteIndex = GetSiteIndex(cellId);
504 Vector sitePos(m_centralPos);
505 const double dist = siteDistances.at(siteIndex);
506 const double angleRad = siteAngles.at(siteIndex) * M_PI / 180;
507 sitePos.x += m_isd * dist * cos(angleRad);
508 sitePos.y += m_isd * dist * sin(angleRad);
509 sitePos.z = m_bsHeight;
510
511 if (GetSectorIndex(cellId) == 0)
512 {
513 sitePosVector->Add(sitePos);
514 }
515
516 // FIXME: Until sites can have more than one antenna array, it is necessary to apply some
517 // distance offset from the site center (gNBs cannot have the same location)
518 Vector bsPos = GetAntennaPosition(sitePos, cellId);
519
520 bsPosVector->Add(bsPos);
521
522 // Store cell center position for plotting the deployment
523 Vector cellCenterPos = GetHexagonalCellCenter(bsPos, cellId);
524 bsCenterVector->Add(cellCenterPos);
525
526 // What about the antenna orientation? It should be dealt with when installing the gNB
527 }
528
529 // To allocate UEs, I need the center of the hexagonal cell.
530 // Allocate UE around the disk of radius isd/3, the diameter of a the
531 // hexagon representing the footprint of a single sector.
532 // Reduce this radius by the min BS-UT distance, to respect that standoff
533 // at the one corner of the sector hexagon where the sector antenna lies.
534 // This results in UTs uniformly distributed in a disc centered on
535 // the sector hexagon; there are no UTs near the vertices of the hexagon.
536 // Spread UEs inside the inner hexagonal radius
537 // Need to weight r to get uniform in the sector hexagon
538 // See https://stackoverflow.com/questions/5837572
539 // Set max = radius^2 here, then take sqrt below
540 const double outerR = m_hexagonalRadius * std::sqrt(3) / 2 - m_minBsUtDistance;
541 m_r->SetAttribute("Min", DoubleValue(0));
542 m_r->SetAttribute("Max", DoubleValue(outerR * outerR));
543 m_theta->SetAttribute("Min", DoubleValue(-1.0 * M_PI));
544 m_theta->SetAttribute("Max", DoubleValue(M_PI));
545
546 // UT position
547
548 uint32_t numUesWithRandomUtHeight = 0;
549 if (percentage != 0)
550 {
551 numUesWithRandomUtHeight = percentage * m_ut.GetN();
552 }
553
554 for (uint32_t utId = 0; utId < m_ut.GetN(); ++utId)
555 {
556 Vector cellCenterPos = bsCenterVector->GetNext();
557 Vector utPos;
558
559 Vector closestSitePosition = GetClosestSitePosition(cellCenterPos, sitePosVector);
560
561 double distance2DToClosestSite = 0;
562
563 // We do not want to take into account the positions that are in the part of the
564 // disk that is far away from the closest site.
565 // To determine whether the position is far away we use
566 // parameter max distance to closest site.
567 uint16_t sanityCounter = 0;
568 do
569 {
570 NS_ABORT_MSG_IF(sanityCounter++ > 1000,
571 "Algorithm needs too many trials to find correct UE position. Please "
572 "check parameters.");
573 double d = std::sqrt(m_r->GetValue());
574 double t = m_theta->GetValue();
575 utPos = cellCenterPos;
576 utPos.x += d * cos(t);
577 utPos.y += d * sin(t);
578 double d_x = utPos.x - closestSitePosition.x;
579 double d_y = utPos.y - closestSitePosition.y;
580 distance2DToClosestSite = sqrt(d_x * d_x + d_y * d_y);
581 } while (distance2DToClosestSite > m_maxUeDistanceToClosestSite);
582
583 if (numUesWithRandomUtHeight > 0)
584 {
585 Ptr<UniformRandomVariable> uniformRandomVariable =
586 CreateObject<UniformRandomVariable>();
587 double Nfl = uniformRandomVariable->GetValue(4, 8);
588 double nfl = uniformRandomVariable->GetValue(1, Nfl);
589
590 if (m_bsHeight == 10)
591 {
592 utPos.z = std::min(3 * (nfl - 1) + 1.5, 9.99);
593 }
594 else
595 {
596 utPos.z = 3 * (nfl - 1) + 1.5;
597 }
598
599 numUesWithRandomUtHeight--;
600 }
601 else
602 {
603 utPos.z = m_utHeight;
604 }
605
606 utPosVector->Add(utPos);
607 }
608
609 mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
610 mobility.SetPositionAllocator(bsPosVector);
611 mobility.Install(m_bs);
612
613 if (speed.GetLength())
614 {
615 ueMobility.SetMobilityModel("ns3::ConstantVelocityMobilityModel");
616 ueMobility.SetPositionAllocator(utPosVector);
617 ueMobility.Install(m_ut);
618
619 for (uint32_t i = 0; i < m_ut.GetN(); i++)
620 {
621 m_ut.Get(i)->GetObject<ConstantVelocityMobilityModel>()->SetVelocity(speed);
622 }
623 }
624 else
625 {
626 mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
627 mobility.SetPositionAllocator(utPosVector);
628 mobility.Install(m_ut);
629 }
630
631 PlotHexagonalDeployment(sitePosVector,
632 bsCenterVector,
633 utPosVector,
634 m_hexagonalRadius,
635 m_resultsDir,
636 m_simTag);
637}
638
639int64_t
641{
642 m_r->SetStream(stream);
643 m_theta->SetStream(stream + 1);
644 return 2;
645}
646
647void
648HexagonalGridScenarioHelper::SetMaxUeDistanceToClosestSite(double maxUeDistanceToClosestSite)
649{
650 NS_ASSERT(maxUeDistanceToClosestSite > 0 + (m_minBsUtDistance > 0) ? m_minBsUtDistance : 0);
651 m_maxUeDistanceToClosestSite = maxUeDistanceToClosestSite;
652}
653
654} // namespace ns3
Vector GetHexagonalCellCenter(const Vector &sitePos, uint16_t cellId) const
Returns the cell center coordinates.
void CreateScenario() override
Create the scenario, with the configured parameter.
~HexagonalGridScenarioHelper() override
~HexagonalGridScenarioHelper
double GetHexagonalCellRadius() const
Gets the radius of the hexagonal cell.
void SetNumRings(uint8_t numRings)
Sets the number of outer rings of sites around the central site.
void CreateScenarioWithMobility(const Vector &speed, double percentage)
This function can be used to create a scenario with UEs with mobility and define a percentage of UEs,...
HexagonalGridScenarioHelper()
HexagonalGridScenarioHelper.
std::size_t m_numBs
Number of base stations to create.
uint16_t GetSiteIndex(std::size_t cellId) const
Gets the site index the queried cell id belongs to.
Vector GetAntennaPosition(const Vector &sitePos, uint16_t cellId) const
Returns the position of the cell antenna.
std::size_t m_numUt
Number of user terminals to create.
std::size_t m_numSites
Number of sites with base stations.
void SetSitesNumber(std::size_t n)
Set number of sites/towers.
uint16_t GetSectorIndex(std::size_t cellId) const
Get the sector index the queried cell id belongs to.
double m_isd
Inter-site distance (ISD) in meters.
uint32_t GetNumSectorsPerSite() const
Gets the number of sectors per site.
double m_bsHeight
Height of gNB nodes.
double m_minBsUtDistance
Minimum distance between BS and UT in meters.
double m_utHeight
Height of UE nodes.