1 package org.opentrafficsim.road.network.lane;
2
3 import java.io.Serializable;
4 import java.util.ArrayList;
5 import java.util.Arrays;
6 import java.util.Iterator;
7 import java.util.List;
8
9 import org.djunits.value.vdouble.scalar.Length;
10 import org.djutils.event.LocalEventProducer;
11 import org.djutils.exceptions.Throw;
12 import org.djutils.exceptions.Try;
13 import org.djutils.logger.CategoryLogger;
14 import org.opentrafficsim.base.Identifiable;
15 import org.opentrafficsim.core.animation.Drawable;
16 import org.opentrafficsim.core.geometry.Bezier;
17 import org.opentrafficsim.core.geometry.Bounds;
18 import org.opentrafficsim.core.geometry.DirectedPoint;
19 import org.opentrafficsim.core.geometry.OtsGeometryException;
20 import org.opentrafficsim.core.geometry.OtsLine3d;
21 import org.opentrafficsim.core.geometry.OtsPoint3d;
22 import org.opentrafficsim.core.geometry.OtsShape;
23 import org.opentrafficsim.core.network.LateralDirectionality;
24 import org.opentrafficsim.core.network.NetworkException;
25 import org.opentrafficsim.road.network.RoadNetwork;
26
27 import nl.tudelft.simulation.dsol.animation.Locatable;
28
29
30
31
32
33
34
35
36
37
38
39 public class CrossSectionElement extends LocalEventProducer implements Locatable, Serializable, Identifiable, Drawable
40 {
41
42 private static final long serialVersionUID = 20150826L;
43
44
45 private final String id;
46
47
48 @SuppressWarnings("checkstyle:visibilitymodifier")
49 protected final CrossSectionLink parentLink;
50
51
52 @SuppressWarnings("checkstyle:visibilitymodifier")
53 protected final List<CrossSectionSlice> crossSectionSlices;
54
55
56 @SuppressWarnings("checkstyle:visibilitymodifier")
57 protected final Length length;
58
59
60 private final OtsLine3d centerLine;
61
62
63 private final OtsShape contour;
64
65
66 public static final double MAXIMUMDIRECTIONERROR = Math.toRadians(0.1);
67
68
69
70
71
72 public static final double FIXUPPOINTPROPORTION = 1.0 / 3;
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87 public CrossSectionElement(final CrossSectionLink parentLink, final String id,
88 final List<CrossSectionSlice> crossSectionSlices) throws OtsGeometryException, NetworkException
89 {
90 Throw.when(parentLink == null, NetworkException.class,
91 "Constructor of CrossSectionElement for id %s, parentLink cannot be null", id);
92 Throw.when(id == null, NetworkException.class, "Constructor of CrossSectionElement -- id cannot be null");
93 for (CrossSectionElement cse : parentLink.getCrossSectionElementList())
94 {
95 Throw.when(cse.getId().equals(id), NetworkException.class,
96 "Constructor of CrossSectionElement -- id %s not unique within the Link", id);
97 }
98 Throw.whenNull(crossSectionSlices, "crossSectionSlices may not be null");
99 this.id = id;
100 this.parentLink = parentLink;
101
102 this.crossSectionSlices = new ArrayList<>(crossSectionSlices);
103 Throw.when(this.crossSectionSlices.size() == 0, NetworkException.class,
104 "CrossSectionElement %s is created with zero slices for %s", id, parentLink);
105 Throw.when(this.crossSectionSlices.get(0).getRelativeLength().si != 0.0, NetworkException.class,
106 "CrossSectionElement %s for %s has a first slice with relativeLength is not equal to 0.0", id, parentLink);
107 Throw.when(
108 this.crossSectionSlices.size() > 1 && this.crossSectionSlices.get(this.crossSectionSlices.size() - 1)
109 .getRelativeLength().ne(this.parentLink.getLength()),
110 NetworkException.class, "CrossSectionElement %s for %s has a last slice with relativeLength is not equal "
111 + "to the length of the parent link",
112 id, parentLink);
113 OtsLine3d proposedCenterLine = null;
114 if (this.crossSectionSlices.size() <= 2)
115 {
116 proposedCenterLine = fixTightInnerCurve(new double[] {0.0, 1.0},
117 new double[] {getDesignLineOffsetAtBegin().getSI(), getDesignLineOffsetAtEnd().getSI()});
118 }
119 else
120 {
121 double[] fractions = new double[this.crossSectionSlices.size()];
122 double[] offsets = new double[this.crossSectionSlices.size()];
123 for (int i = 0; i < this.crossSectionSlices.size(); i++)
124 {
125 fractions[i] = this.crossSectionSlices.get(i).getRelativeLength().si / this.parentLink.getLength().si;
126 offsets[i] = this.crossSectionSlices.get(i).getDesignLineOffset().si;
127 }
128 proposedCenterLine = fixTightInnerCurve(fractions, offsets);
129 }
130
131 List<OtsPoint3d> points = new ArrayList<OtsPoint3d>(Arrays.asList(proposedCenterLine.getPoints()));
132
133 DirectedPoint linkFrom = Try.assign(() -> parentLink.getStartNode().getLocation(), "Cannot happen");
134 double fromDirection = linkFrom.getRotZ();
135 points.remove(0);
136 points.add(0, new OtsPoint3d(linkFrom.x + getDesignLineOffsetAtBegin().getSI() * Math.cos(fromDirection + Math.PI / 2),
137 linkFrom.y + getDesignLineOffsetAtBegin().getSI() * Math.sin(fromDirection + Math.PI / 2)));
138
139 DirectedPoint linkTo = Try.assign(() -> parentLink.getEndNode().getLocation(), "Cannot happen");
140 double toDirection = linkTo.getRotZ();
141 points.remove(points.size() - 1);
142 points.add(new OtsPoint3d(linkTo.x + getDesignLineOffsetAtEnd().getSI() * Math.cos(toDirection + Math.PI / 2),
143 linkTo.y + getDesignLineOffsetAtEnd().getSI() * Math.sin(toDirection + Math.PI / 2)));
144
145 double direction = points.get(0).horizontalDirectionSI(points.get(1));
146 OtsPoint3d extraPointAfterStart = null;
147 if (Math.abs(direction - fromDirection) > MAXIMUMDIRECTIONERROR)
148 {
149
150 OtsPoint3d from = points.get(0);
151 OtsPoint3d next = points.get(1);
152 double distance =
153 Math.min(from.horizontalDistanceSI(next) * FIXUPPOINTPROPORTION, crossSectionSlices.get(0).getWidth().si);
154 extraPointAfterStart = new OtsPoint3d(from.x + Math.cos(fromDirection) * distance,
155 from.y + Math.sin(fromDirection) * distance, from.z + FIXUPPOINTPROPORTION * (next.z - from.z));
156
157 }
158
159 int pointCount = points.size();
160 direction = points.get(pointCount - 2).horizontalDirectionSI(points.get(pointCount - 1));
161 if (Math.abs(direction - toDirection) > MAXIMUMDIRECTIONERROR)
162 {
163
164 OtsPoint3d to = points.get(pointCount - 1);
165 OtsPoint3d before = points.get(pointCount - 2);
166 double distance = Math.min(before.horizontalDistanceSI(to) * FIXUPPOINTPROPORTION,
167 crossSectionSlices.get(Math.max(0, crossSectionSlices.size() - 2)).getWidth().si);
168 points.add(pointCount - 1, new OtsPoint3d(to.x - Math.cos(toDirection) * distance,
169 to.y - Math.sin(toDirection) * distance, to.z - FIXUPPOINTPROPORTION * (before.z - to.z)));
170 }
171 if (null != extraPointAfterStart)
172 {
173 points.add(1, extraPointAfterStart);
174 }
175 this.centerLine = new OtsLine3d(points);
176 this.length = this.centerLine.getLength();
177 this.contour = constructContour(this);
178 this.parentLink.addCrossSectionElement(this);
179
180
181 parentLink.getNetwork().clearLaneChangeInfoCache();
182 }
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199 public CrossSectionElement(final CrossSectionLink parentLink, final String id, final Length lateralOffsetAtBegin,
200 final Length lateralOffsetAtEnd, final Length beginWidth, final Length endWidth,
201 final boolean fixGradualLateralOffset) throws OtsGeometryException, NetworkException
202 {
203 this(parentLink, id, fixLateralOffset(parentLink, lateralOffsetAtBegin, lateralOffsetAtEnd, beginWidth, endWidth,
204 fixGradualLateralOffset));
205 }
206
207
208
209
210
211
212
213
214
215
216
217
218
219 private static List<CrossSectionSlice> fixLateralOffset(final CrossSectionLink parentLink,
220 final Length lateralOffsetAtBegin, final Length lateralOffsetAtEnd, final Length beginWidth, final Length endWidth,
221 final boolean fixGradualLateralOffset)
222 {
223 List<CrossSectionSlice> result = new ArrayList<>();
224 int numPoints = !fixGradualLateralOffset ? 2 : lateralOffsetAtBegin.equals(lateralOffsetAtEnd) ? 2 : 16;
225 Length parentLength = parentLink.getLength();
226 for (int index = 0; index < numPoints; index++)
227 {
228 double fraction = index * 1.0 / (numPoints - 1);
229 Length lengthAtCrossSection = parentLength.times(fraction);
230 double relativeOffsetAtFraction = (1 + Math.sin((fraction - 0.5) * Math.PI)) / 2;
231 Length offsetAtFraction = Length.interpolate(lateralOffsetAtBegin, lateralOffsetAtEnd, relativeOffsetAtFraction);
232 result.add(new CrossSectionSlice(lengthAtCrossSection, offsetAtFraction,
233 Length.interpolate(beginWidth, endWidth, fraction)));
234 }
235 return result;
236 }
237
238
239
240
241
242
243
244
245
246
247 private OtsLine3d fixTightInnerCurve(final double[] fractions, final double[] offsets) throws OtsGeometryException
248 {
249 OtsLine3d linkCenterLine = getParentLink().getDesignLine();
250 for (int i = 1; i < linkCenterLine.size() - 1; i++)
251 {
252 double fraction = linkCenterLine.getVertexFraction(i);
253 int index = 0;
254 while (index < fractions.length - 2 && fraction > fractions[index + 1])
255 {
256 index++;
257 }
258 double w = (fraction - fractions[index]) / (fractions[index + 1] - fractions[index]);
259 double offset = (1.0 - w) * offsets[index] + w * offsets[index + 1];
260 double radius = 1.0;
261 try
262 {
263 radius = linkCenterLine.getProjectedVertexRadius(i).si;
264 }
265 catch (Exception e)
266 {
267 CategoryLogger.always().error(e, "fixTightInnerCurve.getVertexFraction for " + linkCenterLine);
268 }
269 if ((!Double.isNaN(radius))
270 && ((radius < 0.0 && offset < 0.0 && offset < radius) || (radius > 0.0 && offset > 0.0 && offset > radius)))
271 {
272 double offsetStart = getDesignLineOffsetAtBegin().getSI();
273 double offsetEnd = getDesignLineOffsetAtEnd().getSI();
274 DirectedPoint start = linkCenterLine.getLocationFraction(0.0);
275 DirectedPoint end = linkCenterLine.getLocationFraction(1.0);
276 start = new DirectedPoint(start.x - Math.sin(start.getRotZ()) * offsetStart,
277 start.y + Math.cos(start.getRotZ()) * offsetStart, start.z, start.getRotX(), start.getRotY(),
278 start.getRotZ());
279 end = new DirectedPoint(end.x - Math.sin(end.getRotZ()) * offsetEnd,
280 end.y + Math.cos(end.getRotZ()) * offsetEnd, end.z, end.getRotX(), end.getRotY(), end.getRotZ());
281 while (this.crossSectionSlices.size() > 2)
282 {
283 this.crossSectionSlices.remove(1);
284 }
285 return Bezier.cubic(start, end);
286 }
287 }
288 if (this.crossSectionSlices.size() <= 2)
289 {
290 OtsLine3d designLine = this.getParentLink().getDesignLine();
291 if (designLine.size() > 2)
292 {
293
294
295 OtsLine3d line =
296 designLine.offsetLine(getDesignLineOffsetAtBegin().getSI(), getDesignLineOffsetAtEnd().getSI());
297 List<OtsPoint3d> points = new ArrayList<>(Arrays.asList(line.getPoints()));
298 Iterator<OtsPoint3d> it = points.iterator();
299 OtsPoint3d prevPoint = null;
300 while (it.hasNext())
301 {
302 OtsPoint3d point = it.next();
303 if (prevPoint != null && prevPoint.distance(point).si < 1e-4)
304 {
305 it.remove();
306 }
307 prevPoint = point;
308 }
309 return new OtsLine3d(points);
310 }
311 else
312 {
313 DirectedPoint refStart = getParentLink().getStartNode().getLocation();
314 double startRot = refStart.getRotZ();
315 double startOffset = this.crossSectionSlices.get(0).getDesignLineOffset().si;
316 OtsPoint3d start = new OtsPoint3d(refStart.x - Math.sin(startRot) * startOffset,
317 refStart.y + Math.cos(startRot) * startOffset, refStart.z);
318 DirectedPoint refEnd = getParentLink().getEndNode().getLocation();
319 double endRot = refEnd.getRotZ();
320 double endOffset = this.crossSectionSlices.get(this.crossSectionSlices.size() - 1).getDesignLineOffset().si;
321 OtsPoint3d end = new OtsPoint3d(refEnd.x - Math.sin(endRot) * endOffset,
322 refEnd.y + Math.cos(endRot) * endOffset, refEnd.z);
323 return new OtsLine3d(start, end);
324 }
325 }
326 else
327 {
328 for (int i = 0; i < this.crossSectionSlices.size(); i++)
329 {
330 fractions[i] = this.crossSectionSlices.get(i).getRelativeLength().si / this.parentLink.getLength().si;
331 offsets[i] = this.crossSectionSlices.get(i).getDesignLineOffset().si;
332 }
333 return this.getParentLink().getDesignLine().offsetLine(fractions, offsets);
334 }
335 }
336
337
338
339
340
341
342
343
344
345
346
347
348 public CrossSectionElement(final CrossSectionLink parentLink, final String id, final Length lateralOffset,
349 final Length width) throws OtsGeometryException, NetworkException
350 {
351 this(parentLink, id, Arrays.asList(new CrossSectionSlice[] {new CrossSectionSlice(Length.ZERO, lateralOffset, width)}));
352 }
353
354
355
356
357 public final CrossSectionLink getParentLink()
358 {
359 return this.parentLink;
360 }
361
362
363
364
365 public final RoadNetwork getNetwork()
366 {
367 return this.parentLink.getNetwork();
368 }
369
370
371
372
373
374
375 private int calculateSliceNumber(final double fractionalPosition)
376 {
377 double linkLength = this.parentLink.getLength().si;
378 for (int i = 0; i < this.crossSectionSlices.size() - 1; i++)
379 {
380 if (fractionalPosition >= this.crossSectionSlices.get(i).getRelativeLength().si / linkLength
381 && fractionalPosition <= this.crossSectionSlices.get(i + 1).getRelativeLength().si / linkLength)
382 {
383 return i;
384 }
385 }
386 return this.crossSectionSlices.size() - 2;
387 }
388
389
390
391
392
393
394 public final Length getLateralCenterPosition(final double fractionalPosition)
395 {
396 if (this.crossSectionSlices.size() == 1)
397 {
398 return this.getDesignLineOffsetAtBegin();
399 }
400 if (this.crossSectionSlices.size() == 2)
401 {
402 return Length.interpolate(this.getDesignLineOffsetAtBegin(), this.getDesignLineOffsetAtEnd(), fractionalPosition);
403 }
404 int sliceNr = calculateSliceNumber(fractionalPosition);
405 return Length.interpolate(this.crossSectionSlices.get(sliceNr).getDesignLineOffset(),
406 this.crossSectionSlices.get(sliceNr + 1).getDesignLineOffset(), fractionalPosition
407 - this.crossSectionSlices.get(sliceNr).getRelativeLength().si / this.parentLink.getLength().si);
408 }
409
410
411
412
413
414
415 public final Length getLateralCenterPosition(final Length longitudinalPosition)
416 {
417 return getLateralCenterPosition(longitudinalPosition.getSI() / getLength().getSI());
418 }
419
420
421
422
423
424
425 public final Length getWidth(final Length longitudinalPosition)
426 {
427 return getWidth(longitudinalPosition.getSI() / getLength().getSI());
428 }
429
430
431
432
433
434
435 public final Length getWidth(final double fractionalPosition)
436 {
437 if (this.crossSectionSlices.size() == 1)
438 {
439 return this.getBeginWidth();
440 }
441 if (this.crossSectionSlices.size() == 2)
442 {
443 return Length.interpolate(this.getBeginWidth(), this.getEndWidth(), fractionalPosition);
444 }
445 int sliceNr = calculateSliceNumber(fractionalPosition);
446 return Length.interpolate(this.crossSectionSlices.get(sliceNr).getWidth(),
447 this.crossSectionSlices.get(sliceNr + 1).getWidth(), fractionalPosition
448 - this.crossSectionSlices.get(sliceNr).getRelativeLength().si / this.parentLink.getLength().si);
449 }
450
451
452
453
454
455 public final Length getLength()
456 {
457 return this.length;
458 }
459
460
461
462
463
464 public final Length getDesignLineOffsetAtBegin()
465 {
466 return this.crossSectionSlices.get(0).getDesignLineOffset();
467 }
468
469
470
471
472
473 public final Length getDesignLineOffsetAtEnd()
474 {
475 return this.crossSectionSlices.get(this.crossSectionSlices.size() - 1).getDesignLineOffset();
476 }
477
478
479
480
481
482 public final Length getBeginWidth()
483 {
484 return this.crossSectionSlices.get(0).getWidth();
485 }
486
487
488
489
490
491 public final Length getEndWidth()
492 {
493 return this.crossSectionSlices.get(this.crossSectionSlices.size() - 1).getWidth();
494 }
495
496
497
498
499
500 @Override
501 public double getZ()
502 {
503
504 return Try.assign(() -> Locatable.super.getZ(), "Remote exception on calling getZ()");
505 }
506
507
508
509
510
511 public final OtsLine3d getCenterLine()
512 {
513 return this.centerLine;
514 }
515
516
517
518
519
520 public final OtsShape getContour()
521 {
522 return this.contour;
523 }
524
525
526
527
528
529 @Override
530 public final String getId()
531 {
532 return this.id;
533 }
534
535
536
537
538
539 public final String getFullId()
540 {
541 return getParentLink().getId() + "." + this.id;
542 }
543
544
545
546
547
548
549
550
551 public final Length getLateralBoundaryPosition(final LateralDirectionality lateralDirection,
552 final double fractionalLongitudinalPosition)
553 {
554 Length designLineOffset;
555 Length halfWidth;
556 if (this.crossSectionSlices.size() <= 2)
557 {
558 designLineOffset = Length.interpolate(getDesignLineOffsetAtBegin(), getDesignLineOffsetAtEnd(),
559 fractionalLongitudinalPosition);
560 halfWidth = Length.interpolate(getBeginWidth(), getEndWidth(), fractionalLongitudinalPosition).times(0.5);
561 }
562 else
563 {
564 int sliceNr = calculateSliceNumber(fractionalLongitudinalPosition);
565 double startFractionalPosition =
566 this.crossSectionSlices.get(sliceNr).getRelativeLength().si / this.parentLink.getLength().si;
567 designLineOffset = Length.interpolate(this.crossSectionSlices.get(sliceNr).getDesignLineOffset(),
568 this.crossSectionSlices.get(sliceNr + 1).getDesignLineOffset(),
569 fractionalLongitudinalPosition - startFractionalPosition);
570 halfWidth = Length.interpolate(this.crossSectionSlices.get(sliceNr).getWidth(),
571 this.crossSectionSlices.get(sliceNr + 1).getWidth(),
572 fractionalLongitudinalPosition - startFractionalPosition).times(0.5);
573 }
574
575 switch (lateralDirection)
576 {
577 case LEFT:
578 return designLineOffset.minus(halfWidth);
579 case RIGHT:
580 return designLineOffset.plus(halfWidth);
581 default:
582 throw new Error("Bad switch on LateralDirectionality " + lateralDirection);
583 }
584 }
585
586
587
588
589
590
591
592
593 public final Length getLateralBoundaryPosition(final LateralDirectionality lateralDirection,
594 final Length longitudinalPosition)
595 {
596 return getLateralBoundaryPosition(lateralDirection, longitudinalPosition.getSI() / getLength().getSI());
597 }
598
599
600
601
602
603
604
605
606
607 public static OtsShape constructContour(final CrossSectionElement cse) throws OtsGeometryException, NetworkException
608 {
609 OtsPoint3d[] result = null;
610
611 if (cse.crossSectionSlices.size() <= 2)
612 {
613 OtsLine3d crossSectionDesignLine = cse.centerLine;
614 OtsLine3d rightBoundary =
615 crossSectionDesignLine.offsetLine(-cse.getBeginWidth().getSI() / 2, -cse.getEndWidth().getSI() / 2);
616 OtsLine3d leftBoundary =
617 crossSectionDesignLine.offsetLine(cse.getBeginWidth().getSI() / 2, cse.getEndWidth().getSI() / 2);
618 result = new OtsPoint3d[rightBoundary.size() + leftBoundary.size() + 1];
619 int resultIndex = 0;
620 for (int index = 0; index < rightBoundary.size(); index++)
621 {
622 result[resultIndex++] = rightBoundary.get(index);
623 }
624 for (int index = leftBoundary.size(); --index >= 0;)
625 {
626 result[resultIndex++] = leftBoundary.get(index);
627 }
628 result[resultIndex] = rightBoundary.get(0);
629 }
630 else
631 {
632 List<OtsPoint3d> resultList = new ArrayList<>();
633 List<OtsPoint3d> rightBoundary = new ArrayList<>();
634 for (int i = 0; i < cse.crossSectionSlices.size() - 1; i++)
635 {
636 double plLength = cse.getParentLink().getLength().si;
637 double so = cse.crossSectionSlices.get(i).getDesignLineOffset().si;
638 double eo = cse.crossSectionSlices.get(i + 1).getDesignLineOffset().si;
639 double sw2 = cse.crossSectionSlices.get(i).getWidth().si / 2.0;
640 double ew2 = cse.crossSectionSlices.get(i + 1).getWidth().si / 2.0;
641 double sf = cse.crossSectionSlices.get(i).getRelativeLength().si / plLength;
642 double ef = cse.crossSectionSlices.get(i + 1).getRelativeLength().si / plLength;
643 OtsLine3d crossSectionDesignLine =
644 cse.getParentLink().getDesignLine().extractFractional(sf, ef).offsetLine(so, eo);
645 resultList.addAll(Arrays.asList(crossSectionDesignLine.offsetLine(-sw2, -ew2).getPoints()));
646 rightBoundary.addAll(Arrays.asList(crossSectionDesignLine.offsetLine(sw2, ew2).getPoints()));
647 }
648 for (int index = rightBoundary.size(); --index >= 0;)
649 {
650 resultList.add(rightBoundary.get(index));
651 }
652
653 resultList.add(resultList.get(0));
654 result = resultList.toArray(new OtsPoint3d[] {});
655 }
656 return OtsShape.createAndCleanOtsShape(result);
657 }
658
659
660 @Override
661 @SuppressWarnings("checkstyle:designforextension")
662 public DirectedPoint getLocation()
663 {
664 DirectedPoint centroid = this.contour.getLocation();
665 return new DirectedPoint(centroid.x, centroid.y, getZ());
666 }
667
668
669 @Override
670 @SuppressWarnings("checkstyle:designforextension")
671 public Bounds getBounds()
672 {
673 return this.contour.getBounds();
674 }
675
676
677 @Override
678 @SuppressWarnings("checkstyle:designforextension")
679 public String toString()
680 {
681 return String.format("CSE offset %.2fm..%.2fm, width %.2fm..%.2fm", getDesignLineOffsetAtBegin().getSI(),
682 getDesignLineOffsetAtEnd().getSI(), getBeginWidth().getSI(), getEndWidth().getSI());
683 }
684
685
686 @Override
687 @SuppressWarnings("checkstyle:designforextension")
688 public int hashCode()
689 {
690 final int prime = 31;
691 int result = 1;
692 result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
693 result = prime * result + ((this.parentLink == null) ? 0 : this.parentLink.hashCode());
694 return result;
695 }
696
697
698 @Override
699 @SuppressWarnings({"checkstyle:designforextension", "checkstyle:needbraces"})
700 public boolean equals(final Object obj)
701 {
702 if (this == obj)
703 return true;
704 if (obj == null)
705 return false;
706 if (getClass() != obj.getClass())
707 return false;
708 CrossSectionElement other = (CrossSectionElement) obj;
709 if (this.id == null)
710 {
711 if (other.id != null)
712 return false;
713 }
714 else if (!this.id.equals(other.id))
715 return false;
716 if (this.parentLink == null)
717 {
718 if (other.parentLink != null)
719 return false;
720 }
721 else if (!this.parentLink.equals(other.parentLink))
722 return false;
723 return true;
724 }
725 }