Class OverlayOp

  1  
  2  
  3 /*
  4  * Copyright (c) 2016 Vivid Solutions.
  5  *
  6  * All rights reserved. This program and the accompanying materials
  7  * are made available under the terms of the Eclipse Public License 2.0
  8  * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
  9  * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
 10  * and the Eclipse Distribution License is available at
 11  *
 12  * http://www.eclipse.org/org/documents/edl-v10.php.
 13  */
 14 package org.locationtech.jts.operation.overlay;
 15  
 16 import java.util.ArrayList;
 17 import java.util.Iterator;
 18 import java.util.List;
 19  
 20 import org.locationtech.jts.algorithm.PointLocator;
 21 import org.locationtech.jts.geom.Coordinate;
 22 import org.locationtech.jts.geom.Geometry;
 23 import org.locationtech.jts.geom.GeometryFactory;
 24 import org.locationtech.jts.geom.Location;
 25 import org.locationtech.jts.geom.TopologyException;
 26 import org.locationtech.jts.geomgraph.Depth;
 27 import org.locationtech.jts.geomgraph.DirectedEdge;
 28 import org.locationtech.jts.geomgraph.DirectedEdgeStar;
 29 import org.locationtech.jts.geomgraph.Edge;
 30 import org.locationtech.jts.geomgraph.EdgeList;
 31 import org.locationtech.jts.geomgraph.EdgeNodingValidator;
 32 import org.locationtech.jts.geomgraph.Label;
 33 import org.locationtech.jts.geomgraph.Node;
 34 import org.locationtech.jts.geomgraph.PlanarGraph;
 35 import org.locationtech.jts.geomgraph.Position;
 36 import org.locationtech.jts.operation.GeometryGraphOperation;
 37 import org.locationtech.jts.util.Assert;
 38  
 39 /**
 40  * Computes the geometric overlay of two {@link Geometry}s.  The overlay
 41  * can be used to determine any boolean combination of the geometries.
 42  *
 43  * @version 1.7
 44  */
 45 public class OverlayOp
 46   extends GeometryGraphOperation
 47 {
 48 /**
 49  * The spatial functions supported by this class.
 50  * These operations implement various boolean combinations of the resultants of the overlay.
 51  */
 52     
 53   /**
 54    * The code for the Intersection overlay operation.
 55    */
 56   public static final int INTERSECTION  = 1;
 57   
 58   /**
 59    * The code for the Union overlay operation.
 60    */
 61   public static final int UNION         = 2;
 62   
 63   /**
 64    *  The code for the Difference overlay operation.
 65    */
 66   public static final int DIFFERENCE    = 3;
 67   
 68   /**
 69    *  The code for the Symmetric Difference overlay operation.
 70    */
 71   public static final int SYMDIFFERENCE = 4;
 72  
 73   /**
 74    * Computes an overlay operation for 
 75    * the given geometry arguments.
 76    * 
 77    * @param geom0 the first geometry argument
 78    * @param geom1 the second geometry argument
 79    * @param opCode the code for the desired overlay operation
 80    * @return the result of the overlay operation
 81    * @throws TopologyException if a robustness problem is encountered
 82    */
 83   public static Geometry overlayOp(Geometry geom0, Geometry geom1, int opCode)
 84   {
 85     OverlayOp gov = new OverlayOp(geom0, geom1);
 86     Geometry geomOv = gov.getResultGeometry(opCode);
 87     return geomOv;
 88   }
 89  
 90   /**
 91    * Tests whether a point with a given topological {@link Label}
 92    * relative to two geometries is contained in 
 93    * the result of overlaying the geometries using
 94    * a given overlay operation.
 95    * <p>
 96    * The method handles arguments of {@link Location#NONE} correctly
 97    * 
 98    * @param label the topological label of the point
 99    * @param opCode the code for the overlay operation to test
100    * @return true if the label locations correspond to the overlayOpCode
101    */
102   public static boolean isResultOfOp(Label label, int opCode)
103   {
104     int loc0 = label.getLocation(0);
105     int loc1 = label.getLocation(1);
106     return isResultOfOp(loc0, loc1, opCode);
107   }
108  
109   /**
110    * Tests whether a point with given {@link Location}s
111    * relative to two geometries is contained in 
112    * the result of overlaying the geometries using
113    * a given overlay operation.
114    * <p>
115    * The method handles arguments of {@link Location#NONE} correctly
116    *
117    * @param loc0 the code for the location in the first geometry 
118    * @param loc1 the code for the location in the second geometry 
119    * @param overlayOpCode the code for the overlay operation to test
120    * @return true if the locations correspond to the overlayOpCode
121    */
122   public static boolean isResultOfOp(int loc0, int loc1, int overlayOpCode)
123   {
124     if (loc0 == Location.BOUNDARY) loc0 = Location.INTERIOR;
125     if (loc1 == Location.BOUNDARY) loc1 = Location.INTERIOR;
126     switch (overlayOpCode) {
127     case INTERSECTION:
128       return loc0 == Location.INTERIOR
129           && loc1 == Location.INTERIOR;
130     case UNION:
131       return loc0 == Location.INTERIOR
132           || loc1 == Location.INTERIOR;
133     case DIFFERENCE:
134       return loc0 == Location.INTERIOR
135           && loc1 != Location.INTERIOR;
136     case SYMDIFFERENCE:
137       return   (     loc0 == Location.INTERIOR &&  loc1 != Location.INTERIOR)
138             || (     loc0 != Location.INTERIOR &&  loc1 == Location.INTERIOR);
139     }
140     return false;
141   }
142  
143   private final PointLocator ptLocator = new PointLocator();
144   private GeometryFactory geomFact;
145   private Geometry resultGeom;
146  
147   private PlanarGraph graph;
148   private EdgeList edgeList     = new EdgeList();
149  
150   private List resultPolyList   = new ArrayList();
151   private List resultLineList   = new ArrayList();
152   private List resultPointList  = new ArrayList();
153  
154   /**
155    * Constructs an instance to compute a single overlay operation
156    * for the given geometries.
157    * 
158    * @param g0 the first geometry argument
159    * @param g1 the second geometry argument
160    */
161   public OverlayOp(Geometry g0, Geometry g1) {
162     super(g0, g1);
163     graph = new PlanarGraph(new OverlayNodeFactory());
164     /**
165      * Use factory of primary geometry.
166      * Note that this does NOT handle mixed-precision arguments
167      * where the second arg has greater precision than the first.
168      */
169     geomFact = g0.getFactory();
170   }
171  
172   /**
173    * Gets the result of the overlay for a given overlay operation.
174    * <p>
175    * Note: this method can be called once only.
176    * 
177    * @param overlayOpCode the overlay operation to perform
178    * @return the compute result geometry
179    * @throws TopologyException if a robustness problem is encountered
180    */
181   public Geometry getResultGeometry(int overlayOpCode)
182   {
183     computeOverlay(overlayOpCode);
184     return resultGeom;
185   }
186  
187   /**
188    * Gets the graph constructed to compute the overlay.
189    * 
190    * @return the overlay graph
191    */
192   public PlanarGraph getGraph() { return graph; }
193  
194   private void computeOverlay(int opCode)
195   {
196     // copy points from input Geometries.
197     // This ensures that any Point geometries
198     // in the input are considered for inclusion in the result set
199     copyPoints(0);
200     copyPoints(1);
201  
202     // node the input Geometries
203     arg[0].computeSelfNodes(li, false);
204     arg[1].computeSelfNodes(li, false);
205  
206     // compute intersections between edges of the two input geometries
207     arg[0].computeEdgeIntersections(arg[1], li, true);
208  
209     List baseSplitEdges = new ArrayList();
210     arg[0].computeSplitEdges(baseSplitEdges);
211     arg[1].computeSplitEdges(baseSplitEdges);
212     List splitEdges = baseSplitEdges;
213     // add the noded edges to this result graph
214     insertUniqueEdges(baseSplitEdges);
215  
216     computeLabelsFromDepths();
217     replaceCollapsedEdges();
218  
219 //Debug.println(edgeList);
220  
221     /**
222      * Check that the noding completed correctly.
223      * 
224      * This test is slow, but necessary in order to catch robustness failure 
225      * situations.
226      * If an exception is thrown because of a noding failure, 
227      * then snapping will be performed, which will hopefully avoid the problem.
228      * In the future hopefully a faster check can be developed.  
229      * 
230      */
231     EdgeNodingValidator.checkValid(edgeList.getEdges());
232  
233     graph.addEdges(edgeList.getEdges());
234     computeLabelling();
235 //Debug.printWatch();
236     labelIncompleteNodes();
237 //Debug.printWatch();
238 //nodeMap.print(System.out);
239  
240     /**
241      * The ordering of building the result Geometries is important.
242      * Areas must be built before lines, which must be built before points.
243      * This is so that lines which are covered by areas are not included
244      * explicitly, and similarly for points.
245      */
246     findResultAreaEdges(opCode);
247     cancelDuplicateResultEdges();
248  
249     PolygonBuilder polyBuilder = new PolygonBuilder(geomFact);
250     polyBuilder.add(graph);
251     resultPolyList = polyBuilder.getPolygons();
252  
253     LineBuilder lineBuilder = new LineBuilder(this, geomFact, ptLocator);
254     resultLineList = lineBuilder.build(opCode);
255  
256     PointBuilder pointBuilder = new PointBuilder(this, geomFact, ptLocator);
257     resultPointList = pointBuilder.build(opCode);
258  
259     // gather the results from all calculations into a single Geometry for the result set
260     resultGeom = computeGeometry(resultPointList, resultLineList, resultPolyList, opCode);
261   }
262  
263   private void insertUniqueEdges(List edges)
264   {
265     for (Iterator i = edges.iterator(); i.hasNext(); ) {
266       Edge e = (Edge) i.next();
267       insertUniqueEdge(e);
268     }
269   }
270   /**
271    * Insert an edge from one of the noded input graphs.
272    * Checks edges that are inserted to see if an
273    * identical edge already exists.
274    * If so, the edge is not inserted, but its label is merged
275    * with the existing edge.
276    */
277   protected void insertUniqueEdge(Edge e)
278   {
279 //<FIX> MD 8 Oct 03  speed up identical edge lookup
280     // fast lookup
281     Edge existingEdge = edgeList.findEqualEdge(e);
282  
283     // If an identical edge already exists, simply update its label
284     if (existingEdge != null) {
285       Label existingLabel = existingEdge.getLabel();
286  
287       Label labelToMerge = e.getLabel();
288       // check if new edge is in reverse direction to existing edge
289       // if so, must flip the label before merging it
290       if (! existingEdge.isPointwiseEqual(e)) {
291         labelToMerge = new Label(e.getLabel());
292         labelToMerge.flip();
293       }
294       Depth depth = existingEdge.getDepth();
295       // if this is the first duplicate found for this edge, initialize the depths
296       ///*
297       if (depth.isNull()) {
298         depth.add(existingLabel);
299       }
300       //*/
301       depth.add(labelToMerge);
302       existingLabel.merge(labelToMerge);
303 //Debug.print("inserted edge: "); Debug.println(e);
304 //Debug.print("existing edge: "); Debug.println(existingEdge);
305  
306     }
307     else {  // no matching existing edge was found
308       // add this new edge to the list of edges in this graph
309       //e.setName(name + edges.size());
310       //e.getDepth().add(e.getLabel());
311       edgeList.add(e);
312     }
313   }
314  
315   /**
316    * If either of the GeometryLocations for the existing label is
317    * exactly opposite to the one in the labelToMerge,
318    * this indicates a dimensional collapse has happened.
319    * In this case, convert the label for that Geometry to a Line label
320    */
321    /* NOT NEEDED?
322   private void checkDimensionalCollapse(Label labelToMerge, Label existingLabel)
323   {
324     if (existingLabel.isArea() && labelToMerge.isArea()) {
325       for (int i = 0; i < 2; i++) {
326         if (! labelToMerge.isNull(i)
327             &&  labelToMerge.getLocation(i, Position.LEFT)  == existingLabel.getLocation(i, Position.RIGHT)
328             &&  labelToMerge.getLocation(i, Position.RIGHT) == existingLabel.getLocation(i, Position.LEFT) )
329         {
330           existingLabel.toLine(i);
331         }
332       }
333     }
334   }
335   */
336   /**
337    * Update the labels for edges according to their depths.
338    * For each edge, the depths are first normalized.
339    * Then, if the depths for the edge are equal,
340    * this edge must have collapsed into a line edge.
341    * If the depths are not equal, update the label
342    * with the locations corresponding to the depths
343    * (i.e. a depth of 0 corresponds to a Location of EXTERIOR,
344    * a depth of 1 corresponds to INTERIOR)
345    */
346   private void computeLabelsFromDepths()
347   {
348     for (Iterator it = edgeList.iterator(); it.hasNext(); ) {
349       Edge e = (Edge) it.next();
350       Label lbl = e.getLabel();
351       Depth depth = e.getDepth();
352       /**
353        * Only check edges for which there were duplicates,
354        * since these are the only ones which might
355        * be the result of dimensional collapses.
356        */
357       if (! depth.isNull()) {
358         depth.normalize();
359         for (int i = 0; i < 2; i++) {
360           if (! lbl.isNull(i) && lbl.isArea() && ! depth.isNull(i)) {
361           /**
362            * if the depths are equal, this edge is the result of
363            * the dimensional collapse of two or more edges.
364            * It has the same location on both sides of the edge,
365            * so it has collapsed to a line.
366            */
367             if (depth.getDelta(i) == 0) {
368               lbl.toLine(i);
369             }
370             else {
371             /**
372              * This edge may be the result of a dimensional collapse,
373              * but it still has different locations on both sides.  The
374              * label of the edge must be updated to reflect the resultant
375              * side locations indicated by the depth values.
376              */
377               Assert.isTrue(! depth.isNull(i, Position.LEFT), "depth of LEFT side has not been initialized");
378               lbl.setLocation(i, Position.LEFT,   depth.getLocation(i, Position.LEFT));
379               Assert.isTrue(! depth.isNull(i, Position.RIGHT), "depth of RIGHT side has not been initialized");
380               lbl.setLocation(i, Position.RIGHT,  depth.getLocation(i, Position.RIGHT));
381             }
382           }
383         }
384       }
385     }
386   }
387   /**
388    * If edges which have undergone dimensional collapse are found,
389    * replace them with a new edge which is a L edge
390    */
391   private void replaceCollapsedEdges()
392   {
393     List newEdges = new ArrayList();
394     for (Iterator it = edgeList.iterator(); it.hasNext(); ) {
395       Edge e = (Edge) it.next();
396       if (e.isCollapsed()) {
397 //Debug.print(e);
398         it.remove();
399         newEdges.add(e.getCollapsedEdge());
400       }
401     }
402     edgeList.addAll(newEdges);
403   }
404   /**
405    * Copy all nodes from an arg geometry into this graph.
406    * The node label in the arg geometry overrides any previously computed
407    * label for that argIndex.
408    * (E.g. a node may be an intersection node with
409    * a previously computed label of BOUNDARY,
410    * but in the original arg Geometry it is actually
411    * in the interior due to the Boundary Determination Rule)
412    */
413   private void copyPoints(int argIndex)
414   {
415     for (Iterator i = arg[argIndex].getNodeIterator(); i.hasNext(); ) {
416       Node graphNode = (Node) i.next();
417       Node newNode = graph.addNode(graphNode.getCoordinate());
418       newNode.setLabel(argIndex, graphNode.getLabel().getLocation(argIndex));
419     }
420   }
421  
422   /**
423    * Compute initial labelling for all DirectedEdges at each node.
424    * In this step, DirectedEdges will acquire a complete labelling
425    * (i.e. one with labels for both Geometries)
426    * only if they
427    * are incident on a node which has edges for both Geometries
428    */
429   private void computeLabelling()
430   {
431     for (Iterator nodeit = graph.getNodes().iterator(); nodeit.hasNext(); ) {
432       Node node = (Node) nodeit.next();
433 //if (node.getCoordinate().equals(new Coordinate(222, 100)) ) Debug.addWatch(node.getEdges());
434       node.getEdges().computeLabelling(arg);
435     }
436     mergeSymLabels();
437     updateNodeLabelling();
438   }
439   /**
440    * For nodes which have edges from only one Geometry incident on them,
441    * the previous step will have left their dirEdges with no labelling for the other
442    * Geometry.  However, the sym dirEdge may have a labelling for the other
443    * Geometry, so merge the two labels.
444    */
445   private void mergeSymLabels()
446   {
447     for (Iterator nodeit = graph.getNodes().iterator(); nodeit.hasNext(); ) {
448       Node node = (Node) nodeit.next();
449       ((DirectedEdgeStar) node.getEdges()).mergeSymLabels();
450 //node.print(System.out);
451     }
452   }
453   private void updateNodeLabelling()
454   {
455     // update the labels for nodes
456     // The label for a node is updated from the edges incident on it
457     // (Note that a node may have already been labelled
458     // because it is a point in one of the input geometries)
459     for (Iterator nodeit = graph.getNodes().iterator(); nodeit.hasNext(); ) {
460       Node node = (Node) nodeit.next();
461       Label lbl = ((DirectedEdgeStar) node.getEdges()).getLabel();
462       node.getLabel().merge(lbl);
463     }
464   }
465  
466   /**
467    * Incomplete nodes are nodes whose labels are incomplete.
468    * (e.g. the location for one Geometry is null).
469    * These are either isolated nodes,
470    * or nodes which have edges from only a single Geometry incident on them.
471    *
472    * Isolated nodes are found because nodes in one graph which don't intersect
473    * nodes in the other are not completely labelled by the initial process
474    * of adding nodes to the nodeList.
475    * To complete the labelling we need to check for nodes that lie in the
476    * interior of edges, and in the interior of areas.
477    * <p>
478    * When each node labelling is completed, the labelling of the incident
479    * edges is updated, to complete their labelling as well.
480    */
481   private void labelIncompleteNodes()
482   {
483       // int nodeCount = 0;
484     for (Iterator ni = graph.getNodes().iterator(); ni.hasNext(); ) {
485       Node n = (Node) ni.next();
486       Label label = n.getLabel();
487       if (n.isIsolated()) {
488           // nodeCount++;
489         if (label.isNull(0))
490           labelIncompleteNode(n, 0);
491         else
492           labelIncompleteNode(n, 1);
493       }
494       // now update the labelling for the DirectedEdges incident on this node
495       ((DirectedEdgeStar) n.getEdges()).updateLabelling(label);
496 //n.print(System.out);
497     }
498     /*
499     int nPoly0 = arg[0].getGeometry().getNumGeometries();
500     int nPoly1 = arg[1].getGeometry().getNumGeometries();
501     System.out.println("# isolated nodes= " + nodeCount 
502             + "   # poly[0] = " + nPoly0
503             + "   # poly[1] = " + nPoly1);
504     */
505   }
506  
507   /**
508    * Label an isolated node with its relationship to the target geometry.
509    */
510   private void labelIncompleteNode(Node n, int targetIndex)
511   {
512     int loc = ptLocator.locate(n.getCoordinate(), arg[targetIndex].getGeometry());
513       
514       // MD - 2008-10-24 - experimental for now
515 //    int loc = arg[targetIndex].locate(n.getCoordinate());
516     n.getLabel().setLocation(targetIndex, loc);
517   }
518  
519   /**
520    * Find all edges whose label indicates that they are in the result area(s),
521    * according to the operation being performed.  Since we want polygon shells to be
522    * oriented CW, choose dirEdges with the interior of the result on the RHS.
523    * Mark them as being in the result.
524    * Interior Area edges are the result of dimensional collapses.
525    * They do not form part of the result area boundary.
526    */
527   private void findResultAreaEdges(int opCode)
528   {
529     for (Iterator it = graph.getEdgeEnds().iterator(); it.hasNext(); ) {
530       DirectedEdge de = (DirectedEdge) it.next();
531     // mark all dirEdges with the appropriate label
532       Label label = de.getLabel();
533       if (label.isArea()
534           && ! de.isInteriorAreaEdge()
535           && isResultOfOp(
536                 label.getLocation(0, Position.RIGHT),
537                 label.getLocation(1, Position.RIGHT),
538                 opCode)) {
539         de.setInResult(true);
540 //Debug.print("in result "); Debug.println(de);
541       }
542     }
543   }
544   /**
545    * If both a dirEdge and its sym are marked as being in the result, cancel
546    * them out.
547    */
548   private void cancelDuplicateResultEdges()
549   {
550     // remove any dirEdges whose sym is also included
551     // (they "cancel each other out")
552     for (Iterator it = graph.getEdgeEnds().iterator(); it.hasNext(); ) {
553       DirectedEdge de = (DirectedEdge) it.next();
554       DirectedEdge sym = de.getSym();
555       if (de.isInResult() && sym.isInResult()) {
556         de.setInResult(false);
557         sym.setInResult(false);
558 //Debug.print("cancelled "); Debug.println(de); Debug.println(sym);
559       }
560     }
561   }
562   /**
563    * Tests if a point node should be included in the result or not.
564    *
565    * @param coord the point coordinate
566    * @return true if the coordinate point is covered by a result Line or Area geometry
567    */
568   public boolean isCoveredByLA(Coordinate coord)
569   {
570     if (isCovered(coord, resultLineList)) return true;
571     if (isCovered(coord, resultPolyList)) return true;
572     return false;
573   }
574   /**
575    * Tests if an L edge should be included in the result or not.
576    *
577    * @param coord the point coordinate
578    * @return true if the coordinate point is covered by a result Area geometry
579    */
580   public boolean isCoveredByA(Coordinate coord)
581   {
582     if (isCovered(coord, resultPolyList)) return true;
583     return false;
584   }
585   /**
586    * @return true if the coord is located in the interior or boundary of
587    * a geometry in the list.
588    */
589   private boolean isCovered(Coordinate coord, List geomList)
590   {
591     for (Iterator it = geomList.iterator(); it.hasNext(); ) {
592       Geometry geom = (Geometry) it.next();
593       int loc = ptLocator.locate(coord, geom);
594       if (loc != Location.EXTERIOR) return true;
595     }
596     return false;
597   }
598  
599   private Geometry computeGeometry( List resultPointList,
600                                         List resultLineList,
601                                         List resultPolyList,
602                                         int opcode)
603   {
604     List geomList = new ArrayList();
605     // element geometries of the result are always in the order P,L,A
606     geomList.addAll(resultPointList);
607     geomList.addAll(resultLineList);
608     geomList.addAll(resultPolyList);
609     
610     //*
611     if (geomList.isEmpty())
612         return createEmptyResult(opcode, arg[0].getGeometry(), arg[1].getGeometry(), geomFact);
613     //*/
614     
615     // build the most specific geometry possible
616     return geomFact.buildGeometry(geomList);
617   }
618  
619   /**
620    * Creates an empty result geometry of the appropriate dimension,
621    * based on the given overlay operation and the dimensions of the inputs.
622    * The created geometry is always an atomic geometry, 
623    * not a collection.
624    * <p>
625    * The empty result is constructed using the following rules:
626    * <ul>
627    * <li>{@link #INTERSECTION} - result has the dimension of the lowest input dimension
628    * <li>{@link #UNION} - result has the dimension of the highest input dimension
629    * <li>{@link #DIFFERENCE} - result has the dimension of the left-hand input
630    * <li>{@link #SYMDIFFERENCE} - result has the dimension of the highest input dimension
631    * (since the symmetric Difference is the union of the differences).
632    * </ul>
633    * 
634    * @param overlayOpCode the code for the overlay operation being performed
635    * @param a an input geometry
636    * @param b an input geometry
637    * @param geomFact the geometry factory being used for the operation
638    * @return an empty atomic geometry of the appropriate dimension
639    */
640   public static Geometry createEmptyResult(int overlayOpCode, Geometry a, Geometry b, GeometryFactory geomFact)
641   {
642     Geometry result = null;
643     int resultDim = resultDimension(overlayOpCode, a, b);
644  
645     /**
646      * Handles resultSDim = -1, although should not happen
647      */
648     return result =  geomFact.createEmpty(resultDim);
649   }
650   
651   private static int resultDimension(int opCode, Geometry g0, Geometry g1)
652   {
653       int dim0 = g0.getDimension();
654       int dim1 = g1.getDimension();
655       
656       int resultDimension = -1;
657       switch (opCode) {
658       case INTERSECTION: 
659           resultDimension = Math.min(dim0, dim1);
660           break;
661       case UNION: 
662           resultDimension = Math.max(dim0, dim1);
663           break;
664       case DIFFERENCE: 
665           resultDimension = dim0;
666           break;
667       case SYMDIFFERENCE: 
668         /**
669          * This result is chosen because
670          * <pre>
671          * SymDiff = Union(Diff(A, B), Diff(B, A)
672          * </pre>
673          * and Union has the dimension of the highest-dimension argument.
674          */
675           resultDimension = Math.max(dim0, dim1);
676           break;
677       }
678       return resultDimension;
679   }
680 }
681