001/*
002 * A class to create renderables for the ShapeInfo from the GeneralPath
003 * This class is used for the GoogleEarth Renderer
004 * To change this template, choose Tools | Templates
005 * and open the template in the editor.
006 */
007
008package armyc2.c5isr.RenderMultipoints;
009import armyc2.c5isr.JavaTacticalRenderer.TGLight;
010import armyc2.c5isr.JavaLineArray.TacticalLines;
011import java.util.ArrayList;
012import java.util.Arrays;
013
014import armyc2.c5isr.JavaLineArray.Shape2;
015import armyc2.c5isr.JavaLineArray.lineutility;
016import armyc2.c5isr.renderer.utilities.ErrorLogger;
017import armyc2.c5isr.renderer.utilities.RendererException;
018import armyc2.c5isr.renderer.utilities.ShapeInfo;
019import armyc2.c5isr.JavaLineArray.POINT2;
020import static armyc2.c5isr.JavaLineArray.lineutility.CalcDistanceDouble;
021
022import armyc2.c5isr.JavaTacticalRenderer.clsMETOC;
023import armyc2.c5isr.renderer.utilities.Color;
024import armyc2.c5isr.renderer.utilities.RendererSettings;
025import armyc2.c5isr.graphics2d.Area;
026import armyc2.c5isr.graphics2d.BasicStroke;
027import armyc2.c5isr.graphics2d.Line2D;
028import armyc2.c5isr.graphics2d.PathIterator;
029import armyc2.c5isr.graphics2d.Point2D;
030import armyc2.c5isr.graphics2d.Polygon;
031import armyc2.c5isr.graphics2d.Rectangle;
032import armyc2.c5isr.graphics2d.Rectangle2D;
033import armyc2.c5isr.graphics2d.Shape;
034
035/**
036 * Utilities require for GoogleEarth functionality
037*
038 */
039public final class clsUtilityGE {
040    private static final String _className="clsUtilityGE";
041    protected static void setSplineLinetype(TGLight tg)
042    {
043        switch(tg.get_LineType())
044        {
045            case TacticalLines.BRDGHD:
046                tg.set_LineType(TacticalLines.BRDGHD_GE);
047                break;
048            case TacticalLines.HOLD:
049                tg.set_LineType(TacticalLines.HOLD_GE);
050                break;
051            case TacticalLines.ICE_OPENINGS_FROZEN:
052                tg.set_LineType(TacticalLines.ICE_OPENINGS_FROZEN_GE);
053                break;
054            case TacticalLines.ICE_OPENINGS_LEAD:
055                tg.set_LineType(TacticalLines.ICE_OPENINGS_LEAD_GE);
056                break;
057            case TacticalLines.ICE_EDGE_RADAR:
058                tg.set_LineType(TacticalLines.ICE_EDGE_RADAR_GE);
059                break;
060            case TacticalLines.CRACKS_SPECIFIC_LOCATION:
061                tg.set_LineType(TacticalLines.CRACKS_SPECIFIC_LOCATION_GE);
062                break;
063            case TacticalLines.JET:
064                tg.set_LineType(TacticalLines.JET_GE);
065                break;
066            case TacticalLines.STREAM:
067                tg.set_LineType(TacticalLines.STREAM_GE);
068                break;
069            case TacticalLines.FLOOD_TIDE:
070                tg.set_LineType(TacticalLines.FLOOD_TIDE_GE);
071                break;
072            case TacticalLines.EBB_TIDE:
073                tg.set_LineType(TacticalLines.EBB_TIDE_GE);
074                break;
075            case TacticalLines.SEAWALL:
076                tg.set_LineType(TacticalLines.SEAWALL_GE);
077                break;
078            case TacticalLines.JETTY_BELOW_WATER:
079                tg.set_LineType(TacticalLines.JETTY_BELOW_WATER_GE);
080                break;
081            case TacticalLines.JETTY_ABOVE_WATER:
082                tg.set_LineType(TacticalLines.JETTY_ABOVE_WATER_GE);
083                break;
084            case TacticalLines.RAMP_BELOW_WATER:
085                tg.set_LineType(TacticalLines.RAMP_BELOW_WATER_GE);
086                break;
087            case TacticalLines.RAMP_ABOVE_WATER:
088                tg.set_LineType(TacticalLines.RAMP_ABOVE_WATER_GE);
089                break;
090            case TacticalLines.PIER:
091                tg.set_LineType(TacticalLines.PIER_GE);
092                break;
093            case TacticalLines.COASTLINE:
094                tg.set_LineType(TacticalLines.COASTLINE_GE);
095                break;
096            case TacticalLines.DEPTH_CONTOUR:
097                tg.set_LineType(TacticalLines.DEPTH_CONTOUR_GE);
098                break;
099            case TacticalLines.DEPTH_CURVE:
100                tg.set_LineType(TacticalLines.DEPTH_CURVE_GE);
101                break;
102            case TacticalLines.CRACKS:
103                tg.set_LineType(TacticalLines.CRACKS_GE);
104                break;
105            case TacticalLines.ESTIMATED_ICE_EDGE:
106                tg.set_LineType(TacticalLines.ESTIMATED_ICE_EDGE_GE);
107                break;
108            case TacticalLines.ICE_EDGE:
109                tg.set_LineType(TacticalLines.ICE_EDGE_GE);
110                break;
111            case TacticalLines.ISOTHERM:
112                tg.set_LineType(TacticalLines.ISOTHERM_GE);
113                break;
114            case TacticalLines.UPPER_AIR:
115                tg.set_LineType(TacticalLines.UPPER_AIR_GE);
116                break;
117            case TacticalLines.ISOBAR:
118                tg.set_LineType(TacticalLines.ISOBAR_GE);
119                break;
120            case TacticalLines.ISODROSOTHERM:
121                tg.set_LineType(TacticalLines.ISODROSOTHERM_GE);
122                break;
123            case TacticalLines.ISOTACH:
124                tg.set_LineType(TacticalLines.ISOTACH_GE);
125                break;
126            case TacticalLines.ISOPLETHS:
127                tg.set_LineType(TacticalLines.ISOPLETHS_GE);
128                break;
129            default:
130                break;
131        }
132        return;
133    }
134    
135    /**
136     * GE has no capability for dashed lines. This function sets each polyline in the array as a new
137     * polyline broken into points corresponding to the dash pattern
138     * @param polylines
139     * @param shape
140     */
141    private static void createDashedPolylines(ArrayList<ArrayList<Point2D>>polylines, ShapeInfo shape) {
142        try {
143            if (shape.getLineColor() == null) {
144                return;
145            }
146
147            BasicStroke stroke = shape.getStroke();
148            float[] dash = stroke.getDashArray();
149            if (dash == null || dash.length < 2) {
150                return;
151            }
152
153            ArrayList<ArrayList<Point2D>> dashedPolylines = new ArrayList<>();
154
155            for (ArrayList<Point2D> polyline : polylines) {
156                int dashIndex = 0; // Current index in dash array
157                double remainingInIndex = dash[dashIndex]; // Length remaining in current dash array index
158                for (int i = 0; i < polyline.size() - 1; i++) {
159                    Point2D segStartPt = polyline.get(i); // segment start, moves as segment is processed
160                    final Point2D segEndPt = polyline.get(i + 1); // Segment end
161
162                    double segLength; // distance remaining in segment
163                    while ((segLength = lineutility.CalcDistanceDouble(segStartPt, segEndPt)) > 0) {
164                        // If the line segment length is shorter than the current dash then move to the end of the segment continuing to draw or move
165                        // Otherwise move to the end of the current dash and start the next dash there
166                        if (segLength < remainingInIndex) {
167                            if (dashIndex % 2 == 0) {
168                                // Continue line
169                                ArrayList<Point2D> dashedPolyline = new ArrayList<>(Arrays.asList(segStartPt, segEndPt));
170                                dashedPolylines.add(dashedPolyline);
171                            }
172                            remainingInIndex -= segLength;
173                            break; // Next segment
174                        } else {
175                            // Flip to line or space at dashFlipPoint
176                            Point2D dashFlipPoint = lineutility.ExtendAlongLineDouble2(segStartPt, segEndPt, remainingInIndex);
177                            if (dashIndex % 2 == 0) {
178                                // Continue line
179                                ArrayList<Point2D> dashedPolyline = new ArrayList<>(Arrays.asList(segStartPt, dashFlipPoint));
180                                dashedPolylines.add(dashedPolyline);
181                            }
182                            // Next dash
183                            dashIndex++;
184                            if (dashIndex >= dash.length)
185                                dashIndex = 0;
186                            remainingInIndex = dash[dashIndex];
187                            segStartPt = dashFlipPoint;
188                        }
189                    }
190                }
191            }
192            polylines.clear();
193            polylines.addAll(dashedPolylines);
194        } catch (Exception exc) {
195            ErrorLogger.LogException(_className, "createDashedPolylines",
196                    new RendererException("Failed inside createDashedPolylines", exc));
197        }
198    }
199    private static ShapeInfo createSimpleFillShape(TGLight tg,ShapeInfo shape,ArrayList<ArrayList<Point2D>>polylines)
200    {
201        try
202        {
203            BasicStroke s=shape.getStroke();
204            float[]dash=s.getDashArray();
205            if(armyc2.c5isr.JavaTacticalRenderer.clsUtility.isClosedPolygon(tg.get_LineType())==false)
206                if(armyc2.c5isr.JavaTacticalRenderer.clsUtility.IsChange1Area(tg.get_LineType())==false)
207                    return null;
208            if(dash==null || dash.length<2)
209                return null;   
210            if(shape.getFillColor()==null)
211                return null;
212            
213            //if we reach this point we know it is a dashed line so we need a separate fill shape
214            int j=0,k=0;
215            ShapeInfo shape2=new ShapeInfo(shape.getShape());
216            shape2.setShapeType(ShapeInfo.SHAPE_TYPE_FILL);
217            ArrayList<ArrayList<Point2D>>polylines2=new ArrayList();
218            ArrayList<Point2D>polyline=null,polyline2=null;
219            Point2D pt2d=null;
220            s=new BasicStroke(0);
221            shape2.setStroke(s);
222            shape2.setFillColor(shape.getFillColor());            
223            int n=polylines.size();
224            //for(j=0;j<polylines.size();j++)
225            for(j=0;j<n;j++)
226            {
227                polyline=polylines.get(j);
228                polyline2=new ArrayList();
229                int t=polyline.size();
230                //for(k=0;k<polyline.size();k++)
231                for(k=0;k<t;k++)
232                {
233                    pt2d=new Point2D.Double(polyline.get(k).getX(),polyline.get(k).getY());
234                    polyline2.add(pt2d);
235                }
236                polylines2.add(polyline2);
237            }
238            //reset our original dashed shapinfo type to polyline
239            shape.setShapeType(ShapeInfo.SHAPE_TYPE_POLYLINE);
240            //this line will prevent unecessary work by multipointhandler
241            shape.setFillColor(null);
242            shape2.setPolylines(polylines2);            
243//            shape2.setAffineTransform(new AffineTransform());
244            return shape2;
245        }
246        catch (Exception exc) {
247            ErrorLogger.LogException(_className, "createSimpleFillShape",
248                    new RendererException("Failed inside createSimpleFillShape", exc));
249        }
250        return null;
251    }
252    private static ShapeInfo createSimplePatternFillShape(TGLight tg,ShapeInfo shape,ArrayList<ArrayList<Point2D>>polylines)
253    {
254        try
255        {
256            BasicStroke s=shape.getStroke();
257            float[]dash=s.getDashArray();
258            if(armyc2.c5isr.JavaTacticalRenderer.clsUtility.isClosedPolygon(tg.get_LineType())==false)
259                if(armyc2.c5isr.JavaTacticalRenderer.clsUtility.IsChange1Area(tg.get_LineType())==false)
260                    return null;
261            if(dash==null || dash.length<2)
262                return null;
263            if(shape.getPatternFillImage()==null)
264                return null;
265
266            //if we reach this point we know it is a dashed line so we need a separate pattern fill shape
267            int j=0,k=0;
268            ShapeInfo shape2=new ShapeInfo(shape.getShape());
269            shape2.setShapeType(ShapeInfo.SHAPE_TYPE_FILL);
270            ArrayList<ArrayList<Point2D>>polylines2=new ArrayList();
271            ArrayList<Point2D>polyline=null,polyline2=null;
272            Point2D pt2d=null;
273            s=new BasicStroke(0);
274            shape2.setStroke(s);
275            shape2.setPatternFillImage(shape.getPatternFillImage());
276            shape2.setShader(shape.getShader());
277            int n=polylines.size();
278            //for(j=0;j<polylines.size();j++)
279            for(j=0;j<n;j++)
280            {
281                polyline=polylines.get(j);
282                polyline2=new ArrayList();
283                int t=polyline.size();
284                //for(k=0;k<polyline.size();k++)
285                for(k=0;k<t;k++)
286                {
287                    pt2d=new Point2D.Double(polyline.get(k).getX(),polyline.get(k).getY());
288                    polyline2.add(pt2d);
289                }
290                polylines2.add(polyline2);
291            }
292            //reset our original dashed shapinfo type to polyline
293            shape.setShapeType(ShapeInfo.SHAPE_TYPE_POLYLINE);
294            //this line will prevent unecessary work by multipointhandler
295            shape.setPatternFillImage(null);
296            shape.setShader(null);
297            shape2.setPolylines(polylines2);
298//            shape2.setAffineTransform(new AffineTransform());
299            return shape2;
300        }
301        catch (Exception exc) {
302            ErrorLogger.LogException(_className, "createSimplePatternFillShape",
303                    new RendererException("Failed inside createSimplePatternFillShape", exc));
304        }
305        return null;
306    }
307    private static boolean allowFillForThese(TGLight tg)
308    {
309        try
310        {
311            int linetype=tg.get_LineType();
312            int bolMETOC=clsMETOC.IsWeather(tg.get_SymbolId());
313            if(bolMETOC >= 0)            
314                return true;            
315
316            switch(linetype)
317            {
318                case TacticalLines.BBS_AREA:
319                case TacticalLines.BBS_RECTANGLE:
320
321                case TacticalLines.CATK:
322                case TacticalLines.CATKBYFIRE:
323                case TacticalLines.AIRAOA:
324                case TacticalLines.AAAAA:
325                case TacticalLines.MAIN:
326                case TacticalLines.SPT:
327                case TacticalLines.FRONTAL_ATTACK:
328                case TacticalLines.TURNING_MOVEMENT:
329                case TacticalLines.MOVEMENT_TO_CONTACT:
330                
331                case TacticalLines.SARA:
332                case TacticalLines.RANGE_FAN_SECTOR:
333                case TacticalLines.RADAR_SEARCH:
334                case TacticalLines.RANGE_FAN:
335                case TacticalLines.MNFLDFIX:
336                case TacticalLines.TURN_REVD:
337                case TacticalLines.TURN:
338                case TacticalLines.MNFLDDIS:
339                //case TacticalLines.OVERHEAD_WIRE:
340                case TacticalLines.EASY:
341                case TacticalLines.ATDITCHC:
342                case TacticalLines.ATDITCHM:
343                case TacticalLines.FERRY:
344                case TacticalLines.BYDIF:
345                case TacticalLines.BYIMP:
346                case TacticalLines.DEPTH_AREA:
347                    return true;
348                default:
349                    return false;
350            }
351        }
352        catch (Exception exc) {
353            ErrorLogger.LogException(_className, "allowFillForThese",
354                    new RendererException("Failed inside allowFillForThese", exc));
355        }
356        return false;
357    }
358    protected static void SetShapeInfosPolylines(TGLight tg, ArrayList<ShapeInfo> shapeInfos, Object clipBounds)
359    {
360        try
361        {
362            int j=0;
363            Shape shape=null;
364            ShapeInfo shapeInfo=null;
365            ArrayList<ArrayList<Point2D>>polylines=null;
366            int type=-1;
367            ShapeInfo simpleFillShape =null;//diagnostic
368            Boolean isClosed= armyc2.c5isr.JavaTacticalRenderer.clsUtility.isClosedPolygon(tg.get_LineType());
369            int linetype=tg.get_LineType();
370            Color fillColor=null;
371            int n=shapeInfos.size();
372            //for(j=0;j<shapeInfos.size();j++)
373            for(j=0;j<n;j++)
374            {
375                shapeInfo=shapeInfos.get(j);
376                type=shapeInfo.getShapeType();
377                shape=shapeInfo.getShape();
378                if(isClosed==false && type != Shape2.SHAPE_TYPE_FILL)
379                    polylines=createRenderablesFromShape(tg,shape,type,clipBounds);
380                else
381                    polylines=createRenderablesFromShape(tg,shape,type,null);
382                //create a simple fill shape here and change the shape type to SHAPE_TYPE_POLYLINE if it has non-null dash
383                //add the simple fill shape to shapeInfos after the loop
384                if(simpleFillShape==null)
385                    simpleFillShape=createSimpleFillShape(tg,shapeInfo,polylines);
386                if(simpleFillShape==null)
387                    simpleFillShape=createSimplePatternFillShape(tg,shapeInfo,polylines);
388                
389                fillColor=shapeInfo.getFillColor();
390                //if(simpleFillShape!=null || fillColor != null)//the symbol has a basic fill shape
391                if(simpleFillShape!=null)//the symbol has a basic fill shape
392                    if(allowFillForThese(tg)==false)
393                        shapeInfo.setFillColor(null);
394
395                if (!tg.get_UseDashArray())
396                    createDashedPolylines(polylines, shapeInfo);
397                
398                shapeInfo.setPolylines(polylines);
399            }            
400            if(simpleFillShape != null)
401                shapeInfos.add(0,simpleFillShape);
402        }
403        catch (Exception exc) {
404            ErrorLogger.LogException(_className, "SetShapeInfosPolylines",
405                    new RendererException("Failed inside SetShapeInfosPolylines", exc));
406        }
407    }
408    /**
409     * Separates the Shape into separate polylines, eas as an ArrayList of Point2D
410     * @param shape
411     * @return
412     */
413    private static ArrayList<ArrayList<Point2D>>createRenderablesFromShape(TGLight tg, Shape shape, int shapeType, Object clipArea)
414    {
415        ArrayList<Point2D> ptsPoly=new ArrayList();
416        ArrayList<ArrayList<Point2D>>polylines2=new ArrayList<ArrayList<Point2D>>();
417        Point2D ptPoly=null;        
418        try 
419        {
420            //this is not going to work for splines
421            double[] coords = new double[6];
422            for (PathIterator i = shape.getPathIterator(null); !i.isDone(); i.next())
423            {
424                int type = i.currentSegment(coords);
425                switch (type) {
426                    case PathIterator.SEG_MOVETO:
427                        //newshape.moveTo(coords[0], coords[1]);
428                        //finalize the last Polyline and add it to the array
429                        if(ptsPoly.size()>0)
430                        {
431                            if(shapeType==ShapeInfo.SHAPE_TYPE_FILL)
432                            {
433                                if(ptsPoly.get(ptsPoly.size()-1).getX() != ptsPoly.get(0).getX() || 
434                                        ptsPoly.get(ptsPoly.size()-1).getY() != ptsPoly.get(0).getY() )
435                                {
436                                    Point2D pt2d=new Point2D.Double(ptsPoly.get(0).getX(), ptsPoly.get(0).getY());
437                                    ptsPoly.add(pt2d);
438                                }
439                            }                           
440                            if(ptsPoly.size()>1)
441                                polylines2.add(ptsPoly);
442                        }
443                        //start the ArrayList for next Polyline                       
444                        ptsPoly=new ArrayList();
445                        ptPoly=new Point2D.Double(coords[0], coords[1]);
446                        ptsPoly.add(ptPoly);
447                        break;
448                    case PathIterator.SEG_LINETO:
449                        //newshape.lineTo(coords[0], coords[1]);
450                        ptPoly=new Point2D.Double(coords[0],coords[1]);
451                        ptsPoly.add(ptPoly);                        
452                        break;
453                    case PathIterator.SEG_QUADTO: //quadTo was never used
454                        //no idea what to do with this
455                        //newshape.quadTo(coords[0], coords[1], coords[2], coords[3]);
456                        break;
457                    case PathIterator.SEG_CUBICTO:  //curveTo was used for some METOC's
458                        //no idea what to do with these
459                        //newshape.curveTo(coords[0], coords[1], coords[2], coords[3],
460                        //        coords[4], coords[5]);
461                        break;
462                    case PathIterator.SEG_CLOSE:    //closePath was never used
463                        //newshape.closePath();
464                        break;
465                }
466            }
467            if(ptsPoly.size()>1)
468            {
469                //add the last line to the ArrayList
470                //if it is a fill shape then the Google Earth linear ring requires the last point be added
471                if(shapeType==ShapeInfo.SHAPE_TYPE_FILL)
472                {
473                    if(ptsPoly.get(ptsPoly.size()-1).getX() != ptsPoly.get(0).getX() || 
474                            ptsPoly.get(ptsPoly.size()-1).getY() != ptsPoly.get(0).getY() )
475                    {
476                        Point2D pt2d=new Point2D.Double(ptsPoly.get(0).getX(), ptsPoly.get(0).getY());
477                        ptsPoly.add(pt2d);
478                    }
479                }
480                polylines2.add(ptsPoly);                
481            }
482        }
483        catch (Exception exc) {
484            ErrorLogger.LogException(_className, "createRenderableFromShape",
485                    new RendererException("Failed inside createRenderableFromShape", exc));
486        }
487        //return newshape;
488        return polylines2;
489    }   
490    /**
491     * Assumes a convex polygon for the clipping area.
492     * expand the polygon using pixels and a similar algorithm to what flash renderer does for DEPTH AREA
493     * @param pts clipping area to expand
494     * @param expand pixels expansion
495     * @return
496     */
497    protected static ArrayList<Point2D>expandPolygon(ArrayList<Point2D>pts,
498            double expand)
499    {
500        ArrayList<Point2D>lgPoly=null;
501        try
502        {
503            int j=0;
504            Point2D[]destPts=null;
505            boolean isClosed=false;
506            if(pts.get(pts.size()-1).getX()==pts.get(0).getX() && pts.get(pts.size()-1).getY()==pts.get(0).getY())
507            {
508                pts.remove(pts.size()-1);
509                isClosed=true;
510            }
511            ArrayList<POINT2>pts2=clsUtility.Points2DToPOINT2(pts);
512            POINT2 pt0=null,pt1=null,pt2=null,pt3=null;
513            double m=0,m1=0,b=0,b1=0;
514            ArrayList<Line2D>lineSegments=new ArrayList();
515            //n vertical segments
516            int n=pts2.size();
517            //for(j=0;j<pts2.size()-1;j++)
518            for(j=0;j<n-1;j++)
519            {
520                pt0=new POINT2(pts2.get(j));
521                pt1=new POINT2(pts2.get(j+1));
522                //no vertical segments
523                if(pt0.x==pt1.x)
524                {
525                    pt1.x+=1;                                
526                    pts2.set(j+1, pt1);
527                }
528            }
529            POINT2 ptn=pts2.get(pts2.size()-1);
530            pt0=new POINT2(pts2.get(0));
531            //last segment not vertical
532            if(ptn.x==pt0.x)
533            {
534                ptn.x+=1;
535                pts2.set(pts2.size()-1, ptn);
536            }
537            //close pts2
538            pts2.add(pt0);;
539            
540            //POINT2 ptOther=null;
541            //int quadrant=-1,otherQuadrant=-1;
542            Polygon poly=new Polygon();
543            n=pts2.size();
544            //for(j=0;j<pts2.size();j++)
545            for(j=0;j<n;j++)
546                poly.addPoint((int)pts2.get(j).x, (int)pts2.get(j).y);
547            
548            Line2D lineSegment=null;
549            POINT2 midPt=null;
550            //pts2 is closed
551            n=pts2.size();
552            //for(j=0;j<pts2.size()-1;j++)
553            for(j=0;j<n-1;j++)
554            {                
555                pt0=new POINT2(pts2.get(j));
556                pt1=new POINT2(pts2.get(j+1));                
557                m=(pt0.y-pt1.y)/(pt0.x-pt1.x);
558                //m1=-1/m;
559                if(Math.abs(m)<1)
560                {
561                    pt2=lineutility.ExtendDirectedLine(pt0, pt1, pt0, lineutility.extend_above, expand);
562                    pt3=lineutility.ExtendDirectedLine(pt0, pt1, pt1, lineutility.extend_above, expand);
563                    midPt=lineutility.MidPointDouble(pt2, pt3, 0);
564                    //we want the polygon to not contain the extended points
565                    if(poly.contains(midPt.x, midPt.y))
566                    {
567                        pt2=lineutility.ExtendDirectedLine(pt0, pt1, pt0, lineutility.extend_below, expand);
568                        pt3=lineutility.ExtendDirectedLine(pt0, pt1, pt1, lineutility.extend_below, expand);                        
569                    }                                        
570                }
571                else
572                {
573                    pt2=lineutility.ExtendDirectedLine(pt0, pt1, pt0, lineutility.extend_left, expand);
574                    pt3=lineutility.ExtendDirectedLine(pt0, pt1, pt1, lineutility.extend_left, expand);
575                    midPt=lineutility.MidPointDouble(pt2, pt3, 0);
576                    //we want the polygon to not contain the extended points
577                    if(poly.contains(midPt.x, midPt.y))
578                    {
579                        pt2=lineutility.ExtendDirectedLine(pt0, pt1, pt0, lineutility.extend_right, expand);
580                        pt3=lineutility.ExtendDirectedLine(pt0, pt1, pt1, lineutility.extend_right, expand);                        
581                    }                                                            
582                }
583                lineSegment=new Line2D.Double(pt2.x, pt2.y, pt3.x, pt3.y);
584                lineSegments.add(lineSegment);
585            }
586            //we will intersect the line segments to form an expanded polygon
587            ArrayList<POINT2> expandPts=new ArrayList();
588            Line2D thisLine=null,nextLine=null;
589            double x1=0,y1=0,x2=0,y2=0,x=0,y=0;
590            int t=lineSegments.size();
591            //for(j=0;j<lineSegments.size();j++)
592            for(j=0;j<t;j++)
593            {
594                thisLine=lineSegments.get(j);
595                x1=thisLine.getX1();
596                y1=thisLine.getY1();
597                x2=thisLine.getX2();
598                y2=thisLine.getY2();
599                //thisLine line equation
600                m=(y1-y2)/(x1-x2);
601                b=y1-m*x1;
602                
603                if(j==lineSegments.size()-1)
604                    nextLine=lineSegments.get(0);
605                else
606                    nextLine=lineSegments.get(j+1);
607                
608                x1=nextLine.getX1();
609                y1=nextLine.getY1();
610                x2=nextLine.getX2();
611                y2=nextLine.getY2();
612                //nextLine line equation
613                m1=(y1-y2)/(x1-x2);
614                b1=y1-m1*x1;
615                
616                //intersect thisLine with nextLine
617                if(m != m1)
618                {
619                    x=(b1-b)/(m-m1);    //cannot blow up
620                    y=(m*x+b);
621                }
622                else    //this should not happen
623                {
624                    x=thisLine.getX2();
625                    y=thisLine.getY2();
626                }
627                expandPts.add(new POINT2(x,y));
628            }           
629            lgPoly=new ArrayList();
630            t=expandPts.size();
631            //for(j=0;j<expandPts.size();j++)            
632            for(j=0;j<t;j++)            
633                lgPoly.add(new Point2D.Double(expandPts.get(j).x, expandPts.get(j).y));
634            
635            //close the aray if the original clipping array if applicable
636            if(isClosed)
637                lgPoly.add( new Point2D.Double( lgPoly.get(0).getX(),lgPoly.get(0).getY() ) );
638        }
639        catch (Exception exc) 
640        {
641            ErrorLogger.LogException(_className, "expandPolygon2",
642                    new RendererException("Failed inside expandPolygon2", exc));
643        }
644        return lgPoly;
645    }
646    /**
647     * use cheap algorithm to expand polygons, works best on regular 4+ sided convex polygons
648     * used primarily for expanding the original clipping areas. After clipping a tactical line against
649     * the expanded clipping area, the original clipping area can be used to drop the clip lines
650     * @param pts points to expand, usually a clipping area
651     * @param expandX X expansion factor, e.g 10% growth would be 1.1
652     * @param expandY Y expansion factor
653     * @return points for the expanded polygon
654     */
655    protected static ArrayList<Point2D>expandPolygon2(ArrayList<Point2D>pts,
656            double expandX, 
657            double expandY)
658    {
659        ArrayList<Point2D>lgPoly=null;
660        try
661        {
662//            AffineTransform at=new AffineTransform();
663//            at.setToIdentity();        
664            //get the center of the pts using an average
665            double avgX=0,avgY=0,totalX=0,totalY=0;
666            int j=0;
667            boolean isClosed=false;
668            //open the array, remove the last point if necessary
669            if(pts.get(pts.size()-1).getX()==pts.get(0).getX() && pts.get(pts.size()-1).getY()==pts.get(0).getY())
670            {
671                pts.remove(pts.size()-1);
672                isClosed=true;
673            }
674            //asumes open array
675            int n=pts.size();
676            //for(j=0;j<pts.size();j++)
677            for(j=0;j<n;j++)
678            {
679                totalX+=pts.get(j).getX();
680                totalY+=pts.get(j).getY();
681            }
682            avgX=totalX/pts.size();
683            avgY=totalY/pts.size();
684            Point2D.Double[]srcPts=new Point2D.Double[pts.size()];
685            //for(j=0;j<pts.size();j++)
686            n=pts.size();
687            for(j=0;j<n;j++)
688            {
689                srcPts[j]=new Point2D.Double(pts.get(j).getX(),pts.get(j).getY());
690            }
691            Point2D[]destPts=new Point2D[pts.size()];
692            //translate the points to crcumscribe 0,0
693//            at.translate(-avgY, -avgY);//ideally would be close to 0        
694//            at.transform(srcPts, 0, destPts, 0, srcPts.length);
695//            at.setToIdentity();
696            //scale the points by 10%
697//            at.scale(expandX, expandY);
698//            at.transform(destPts, 0, destPts, 0, destPts.length);
699//            at.setToIdentity();
700//            at.translate(avgY, avgY);
701//            at.transform(destPts, 0, destPts, 0, destPts.length);
702            lgPoly=new ArrayList<Point2D>();
703            int t=destPts.length;
704            //for(j=0;j<destPts.length;j++)
705            for(j=0;j<t;j++)
706            {
707                lgPoly.add(destPts[j]);
708            }
709            //close the aray if the original clipping array was closed
710            if(isClosed)
711                lgPoly.add(new Point2D.Double(destPts[0].getX(),destPts[0].getY()));
712        }
713        catch (Exception exc) {
714            ErrorLogger.LogException(_className, "expandPolygon",
715                    new RendererException("Failed inside expandPolygon", exc));
716        }
717        return lgPoly;
718    }
719    /**
720     * @deprecated 
721     * For tactical lines break up the arraylists into separate arraylists within the bounds.
722     * This was added for the Google Earth 3D map because small scales cut off and we want the clip lines
723     * to not be visible.
724     * @param ptsPoly
725     * @param clipBounds
726     * @return 
727     */
728    private static ArrayList<ArrayList<Point2D>> ptsPolyToPtsPoly(TGLight tg, ArrayList<ArrayList<Point2D>>ptsPoly,
729            Rectangle2D clipBounds)
730    {
731        ArrayList<ArrayList<Point2D>> ptsPoly2=null;
732        try
733        {
734            if(armyc2.c5isr.JavaTacticalRenderer.clsUtility.IsChange1Area(tg.get_LineType())==true)
735                return ptsPoly;
736            
737            int j=0,k=0;
738            ArrayList<Point2D>pts=null;
739            ArrayList<Point2D>addPts=null;
740            Point2D pt0=null;
741            Point2D pt1=null;
742            Line2D line=null;
743            ptsPoly2=new ArrayList();
744            int n=ptsPoly.size();
745            //for(j=0;j<ptsPoly.size();j++)
746            for(j=0;j<n;j++)
747            {
748                addPts=null;
749                pts=ptsPoly.get(j);
750                //find the first point inside the clipbounds
751                int t=pts.size();
752                //for(k=0;k<pts.size()-1;k++)
753                for(k=0;k<t-1;k++)
754                {
755                    pt0=pts.get(k);
756                    pt1=pts.get(k+1);
757                                        
758                    line=new Line2D.Double(pt0,pt1);
759                    //both points out of bounds, do not add points
760                    if(clipBounds.contains(pt0)==false && clipBounds.contains(pt1)==false)
761                    {                                                
762                        if(clipBounds.intersectsLine(line)==false)
763                        {
764                            addPts=null;
765                            continue;
766                        }
767                        else
768                        {
769                            if(addPts==null)
770                            {
771                                addPts=new ArrayList();
772                                addPts.add(pt0);
773                            }
774                            if(addPts.contains(pt0)==false)
775                                addPts.add(pt0);
776                            
777                            addPts.add(pt1);
778                            ptsPoly2.add(addPts);
779                            addPts=null;
780                        }
781                    }
782                    else if(clipBounds.contains(pt0)==false && clipBounds.contains(pt1)==true)
783                    {
784                        if(addPts == null)
785                        {
786                            addPts=new ArrayList();
787                            addPts.add(pt0);
788                        }
789                        if(addPts.contains(pt0)==false)
790                            addPts.add(pt0);
791                        
792                        addPts.add(pt1);
793                    }
794                    else if(clipBounds.contains(pt0)==true && clipBounds.contains(pt1)==true)
795                    {
796                        if(addPts==null)
797                        {
798                            addPts=new ArrayList();
799                            addPts.add(pt0);
800                        }
801                        if(addPts.contains(pt0)==false)
802                            addPts.add(pt0);
803                        
804                        addPts.add(pt1);                        
805                    }
806                    else if(clipBounds.contains(pt0)==true && clipBounds.contains(pt1)==false)
807                    {
808                        if(addPts==null)
809                        {
810                            addPts=new ArrayList();
811                            addPts.add(pt0);
812                        }
813                        if(addPts.contains(pt0)==false)
814                            addPts.add(pt0);
815                        //end the current polyline
816                        //and add it to the array list
817                        addPts.add(pt1);
818                        ptsPoly2.add(addPts);                                                
819                        addPts=null;                                                
820                    }                                            
821                }
822                //add the final array list
823                if(addPts != null && addPts.size()>0)
824                    ptsPoly2.add(addPts);
825            }
826        }
827        catch (Exception exc) {
828            ErrorLogger.LogException(_className, "ptsPolyToPtsPoly",
829                    new RendererException("Failed inside ptsPolyToPtsPoly", exc));
830        }
831        return ptsPoly2;
832    }
833    /**
834     * @deprecated 
835     * function to remove the clip lines from the polygon that was clipped
836     * @param ptsPoly the clipped points array
837     * @param clipBounds the clipping points
838     * @return 
839     */
840    private static ArrayList<ArrayList<Point2D>> ptsPolyToPtsPoly(TGLight tg, ArrayList<ArrayList<Point2D>>ptsPoly,
841            ArrayList<Point2D> clipBounds)//was rectangle2D clipBounds
842    {
843        ArrayList<ArrayList<Point2D>> ptsPoly2=null;
844        try
845        {
846            if(armyc2.c5isr.JavaTacticalRenderer.clsUtility.IsChange1Area(tg.get_LineType())==true)
847                return ptsPoly;
848            
849            int j=0,k=0;
850            ArrayList<Point2D>pts=null;
851            ArrayList<Point2D>addPts=null;
852            Point2D pt0=null;
853            Point2D pt1=null;
854            Line2D line=null;
855            ptsPoly2=new ArrayList();
856            Polygon clipPoly=new Polygon();
857            
858            //ArrayList<Point2D>ptsClipArea=null;
859            int n=clipBounds.size();
860            //for(j=0;j<clipBounds.size();j++)    
861            for(j=0;j<n;j++)    
862            {
863                clipPoly.addPoint((int)clipBounds.get(j).getX(), (int)clipBounds.get(j).getY());
864            }
865            n=ptsPoly.size();
866            //for(j=0;j<ptsPoly.size();j++)
867            for(j=0;j<n;j++)
868            {
869                addPts=null;
870                pts=ptsPoly.get(j);
871                //find the first point inside the clipbounds
872                int t=pts.size();
873                //for(k=0;k<pts.size()-1;k++)
874                for(k=0;k<t-1;k++)
875                {
876                    pt0=pts.get(k);
877                    pt1=pts.get(k+1);
878                    line=new Line2D.Double(pt0,pt1);
879                    //both points out of bounds, do not add points
880                    if(clipPoly.contains(pt0)==false && clipPoly.contains(pt1)==false)
881                    {                                                
882                        if(lineIntersectsClipArea(line,clipBounds)==false)
883                        {
884                            addPts=null;
885                            continue;
886                        }
887                        else
888                        {
889                            if(addPts==null)
890                            {
891                                addPts=new ArrayList();
892                                addPts.add(pt0);
893                            }
894                            if(addPts.contains(pt0)==false)
895                                addPts.add(pt0);
896                            
897                            addPts.add(pt1);
898                            ptsPoly2.add(addPts);
899                            addPts=null;
900                        }
901                    }
902                    else if(clipPoly.contains(pt0)==false && clipPoly.contains(pt1)==true)
903                    {
904                        if(addPts == null)
905                        {
906                            addPts=new ArrayList();
907                            addPts.add(pt0);
908                        }
909                        if(addPts.contains(pt0)==false)
910                            addPts.add(pt0);
911                        
912                        addPts.add(pt1);
913                    }
914                    else if(clipPoly.contains(pt0)==true && clipPoly.contains(pt1)==true)
915                    {
916                        if(addPts==null)
917                        {
918                            addPts=new ArrayList();
919                            addPts.add(pt0);
920                        }
921                        if(addPts.contains(pt0)==false)
922                            addPts.add(pt0);
923                        
924                        addPts.add(pt1);                        
925                    }
926                    else if(clipPoly.contains(pt0)==true && clipPoly.contains(pt1)==false)
927                    {
928                        if(addPts==null)
929                        {
930                            addPts=new ArrayList();
931                            addPts.add(pt0);
932                        }
933                        if(addPts.contains(pt0)==false)
934                            addPts.add(pt0);
935                        //end the current polyline
936                        //and add it to the array list
937                        addPts.add(pt1);
938                        ptsPoly2.add(addPts);                                                
939                        addPts=null;                                                
940                    }                                            
941                }
942                //add the final array list
943                if(addPts != null && addPts.size()>0)
944                    ptsPoly2.add(addPts);
945            }
946        }
947        catch (Exception exc) {
948            ErrorLogger.LogException(_className, "ptsPolyToPtsPoly",
949                    new RendererException("Failed inside ptsPolyToPtsPoly", exc));
950        }
951        return ptsPoly2;
952    }    
953    /**
954     * removes leading or trailing segments after the points were clipped
955     * @param tg
956     * @param clipArea 
957     */
958    protected static void removeTrailingPoints(TGLight tg, Object clipArea)
959    {
960        try
961        {
962            boolean isClosed= armyc2.c5isr.JavaTacticalRenderer.clsUtility.isClosedPolygon(tg.get_LineType());
963            if(isClosed)
964                return;
965            
966            Polygon poly=new Polygon();
967            Area area=null;
968            Rectangle2D clipBounds=null;
969            ArrayList<Point2D>clipPoints=null;
970            Point2D pt2d=null;
971            int j=0;
972            if(clipArea==null)
973                return;
974
975            if(clipArea.getClass().isAssignableFrom(Rectangle2D.Double.class))
976            {
977                clipBounds=(Rectangle2D.Double)clipArea;
978            }
979            else if(clipArea.getClass().isAssignableFrom(Rectangle.class))
980            {
981                //clipBounds=(Rectangle2D)clipArea;
982                Rectangle rectx=(Rectangle)clipArea;
983                clipBounds=new Rectangle2D.Double(rectx.x,rectx.y,rectx.width,rectx.height);
984            }
985            else if(clipArea.getClass().isAssignableFrom(ArrayList.class))
986            {
987                clipPoints=(ArrayList<Point2D>)clipArea;            
988            }
989            if(clipBounds != null)
990            {
991                clipPoints=new ArrayList<Point2D>();
992                clipPoints.add(new Point2D.Double(clipBounds.getX(),clipBounds.getY()));                
993                clipPoints.add(new Point2D.Double(clipBounds.getX()+clipBounds.getWidth(),clipBounds.getY()));                
994                clipPoints.add(new Point2D.Double(clipBounds.getX()+clipBounds.getWidth(),clipBounds.getY()+clipBounds.getHeight()));                
995                clipPoints.add(new Point2D.Double(clipBounds.getX(),clipBounds.getY()+clipBounds.getHeight()));                
996                clipPoints.add(new Point2D.Double(clipBounds.getX(),clipBounds.getY()));                
997            }   
998
999            Point2D ptLast=clipPoints.get(clipPoints.size()-1);
1000            Point2D pt02d=clipPoints.get(0);
1001            Point2D pt12d=null;
1002            //close the area
1003            if(pt02d.getX() != ptLast.getX() || pt02d.getY() != ptLast.getY())
1004            {
1005                clipPoints.add(new Point2D.Double(pt02d.getX(),pt02d.getY()));
1006                //poly.addPoint((int)pt02d.getX(),(int)pt02d.getY());
1007            }
1008            //fill the polygon
1009            int n=clipPoints.size();
1010            //for(j=0;j<clipPoints.size();j++)
1011            for(j=0;j<n;j++)
1012            {
1013                pt02d=clipPoints.get(j);            
1014                poly.addPoint((int)pt02d.getX(), (int)pt02d.getY());
1015            }
1016            area=new Area(poly);
1017            Line2D line=null;
1018            POINT2 pt0=null,pt1=null;
1019            boolean intersects=false;
1020            int frontIndex=0,backIndex=tg.Pixels.size()-1;
1021            //breaks at the first leading segment that intersects the clip area
1022            n=tg.Pixels.size();
1023            //for(j=0;j<tg.Pixels.size()-1;j++)
1024            for(j=0;j<n-1;j++)
1025            {
1026               pt0=tg.Pixels.get(j);
1027               pt1=tg.Pixels.get(j+1);
1028               line=new Line2D.Double(pt0.x, pt0.y, pt1.x, pt1.y);
1029               intersects=lineIntersectsClipArea(line, clipPoints);
1030               if(intersects==true)
1031               {
1032                   frontIndex=j;
1033                   break;
1034               }
1035               else if(area.contains((int)pt0.x,(int)pt0.y) || area.contains((int)pt1.x,(int)pt1.y))
1036               {
1037                   frontIndex=j;
1038                   break;               
1039               }           
1040            }
1041            //breaks at the first trailing segment that intersects the clip area
1042            n=tg.Pixels.size();
1043            //for(j=tg.Pixels.size()-1;j>0;j--)
1044            for(j=n-1;j>0;j--)
1045            {
1046               pt0=tg.Pixels.get(j);
1047               pt1=tg.Pixels.get(j-1);
1048               line=new Line2D.Double(pt0.x, pt0.y, pt1.x, pt1.y);
1049               intersects=lineIntersectsClipArea(line, clipPoints);
1050               if(intersects==true)
1051               {
1052                   backIndex=j;
1053                   break;
1054               }
1055               else if(area.contains((int)pt0.x,(int)pt0.y) || area.contains((int)pt1.x,(int)pt1.y))
1056               {
1057                   backIndex=j;
1058                   break;               
1059               }           
1060            }
1061            ArrayList<POINT2>pts=new ArrayList();
1062            for(j=frontIndex;j<=backIndex;j++)
1063            {
1064                pt0=new POINT2(tg.Pixels.get(j));
1065                pts.add(pt0);
1066            }
1067            tg.Pixels=pts;           
1068        }
1069        catch(Exception exc)
1070        {
1071            ErrorLogger.LogException("clsRenderer" ,"removeTrailingPoints",
1072                    new RendererException("Failed inside removeTrailingPoints", exc));
1073        }
1074    }
1075/**
1076     * tests of a Line2D intersects a polygon by using line.intersectsLine on each segment of the polygon
1077     * assumes clip clipping area was parsed to shift points of vertical segments to make them not vertical
1078     * @param line a clipping line in the clipping polygon
1079     * @param clipPts array of clip points assumed to be closed
1080     * @return true if the line intersects the clip bounds
1081     */
1082    private static boolean lineIntersectsClipArea(Line2D line, 
1083            ArrayList<Point2D> clipPts)
1084    {
1085        boolean result=false;
1086        try
1087        {
1088            int j=0;           
1089            
1090            //test if polygon contains an end point
1091            Polygon poly=new Polygon();
1092            int n=clipPts.size();
1093            //for(j=0;j<clipPts.size();j++)            
1094            for(j=0;j<n;j++)            
1095                poly.addPoint((int)clipPts.get(j).getX(),(int)clipPts.get(j).getY());
1096            
1097            if(poly.contains(line.getX1(),line.getY1()))
1098                return true;
1099            if(poly.contains(line.getX2(),line.getY2()))
1100                return true;
1101            //end section
1102            
1103            Line2D currentSegment=null;
1104            n=clipPts.size();
1105            //for(j=0;j<clipPts.size()-1;j++)
1106            for(j=0;j<n-1;j++)
1107            {
1108                currentSegment=new Line2D.Double(clipPts.get(j).getX(),clipPts.get(j).getY(),clipPts.get(j+1).getX(),clipPts.get(j+1).getY());
1109                if(line.intersectsLine(currentSegment)==true)
1110                    return true;            
1111            }
1112            //if the clipPts are not closed then the above loop did not test the closing segment            
1113            Point2D pt0=clipPts.get(0);
1114            Point2D ptLast=clipPts.get(clipPts.size()-1);
1115            //int n=clipPts.size()-1;            
1116            if(pt0.getX()!=ptLast.getX() || pt0.getY()!=ptLast.getY())
1117            {
1118                //currentSegment=new Line2D.Double(clipPts.get(n).getX(),clipPts.get(n).getY(),clipPts.get(0).getX(),clipPts.get(0).getY());
1119                currentSegment=new Line2D.Double(ptLast.getX(),ptLast.getY(),pt0.getX(),pt0.getY());
1120                if(line.intersectsLine(currentSegment)==true)
1121                    return true;                            
1122            }
1123        }
1124        catch (Exception exc) {
1125            ErrorLogger.LogException(_className, "lineIntersectsClipArea",
1126                    new RendererException("Failed inside lineIntersectsClipArea", exc));
1127        }
1128        return result;
1129    }
1130    /**
1131     * returns true if segment data set for MSR, ASR, Boundary
1132     * @param tg
1133     * @return 
1134     */
1135    protected static boolean segmentColorsSet(TGLight tg)
1136    {
1137        try
1138        {
1139            switch(tg.get_LineType())
1140            {
1141                case TacticalLines.BOUNDARY:
1142                case TacticalLines.MSR:
1143                case TacticalLines.ASR:
1144                case TacticalLines.ROUTE:
1145                    break;
1146                default:
1147                    return false;
1148            }
1149            String strH=tg.get_H();
1150            if(strH==null || strH.isEmpty())
1151                return false;
1152            String[] strs=strH.split(",");
1153            if(strs.length>1)
1154                return true;            
1155        }
1156        catch(Exception exc)
1157        {
1158            ErrorLogger.LogException(_className, "segmentColorsSet",
1159                    new RendererException("Failed inside segmentColorsSet", exc));
1160        }
1161        return false;
1162    }
1163    /**
1164     * Use clipping rectangle or clip points to build a zoom factor if the client zoomed in after the initial render.
1165     * Multiply the geo segmenting interval by this factor.
1166     * @param rect
1167     * @param clipPoints
1168     * @param pixels
1169     * @return 
1170     */
1171    protected static double getZoomFactor(Rectangle2D rect, ArrayList<Point2D> clipPoints, ArrayList<POINT2>pixels)
1172    {
1173        double factor=-1;
1174        try
1175        {
1176            if(pixels==null || pixels.size()<2)
1177                return factor;
1178            if(clipPoints==null && rect==null)
1179                return factor;
1180            double maxLengthPixels=0, maxLengthClipArea=0,temp=0;
1181            int j=0;
1182            Point2D pt2d0=null,pt2d1=null;POINT2 pt0=null, pt1=null;
1183            for(j=0;j<pixels.size()-1;j++)
1184            {
1185               pt0=pixels.get(j);
1186               pt1=pixels.get(j+1);
1187               temp=lineutility.CalcDistanceDouble(pt0, pt1);
1188               if(temp>maxLengthPixels)
1189                   maxLengthPixels=temp;
1190            }
1191            temp=0;
1192            if(clipPoints != null)
1193            {
1194                for(j=0;j<clipPoints.size()-1;j++)
1195                {
1196                   pt2d0=clipPoints.get(j);
1197                   pt2d1=clipPoints.get(j+1);
1198                   pt0=new POINT2(pt2d0.getX(),pt2d0.getY());
1199                   pt1=new POINT2(pt2d1.getX(),pt2d1.getY());
1200                   temp=lineutility.CalcDistanceDouble(pt0, pt1);
1201                }
1202            }
1203            else if(rect != null)
1204            {
1205                temp=rect.getMaxX()-rect.getMinX();
1206                if(temp < rect.getMaxY()-rect.getMinY())
1207                    temp=rect.getMaxY()-rect.getMinY();
1208            }
1209            if(temp>maxLengthClipArea)
1210                maxLengthClipArea=temp;
1211            if(maxLengthPixels > 0 && maxLengthClipArea > 0)
1212                factor=maxLengthClipArea/maxLengthPixels;
1213        }
1214        catch(Exception exc)
1215        {
1216            ErrorLogger.LogException(_className, "getZoomFactor",
1217                    new RendererException("Failed inside getZoomFactor", exc));
1218        }
1219        return factor;
1220    }
1221    
1222}