001package armyc2.c5isr.renderer.utilities;
002
003
004import android.graphics.Point;
005import android.graphics.RectF;
006
007import java.text.SimpleDateFormat;
008import java.util.ArrayList;
009import java.util.Date;
010import java.util.Locale;
011import java.util.TimeZone;
012import java.util.regex.Pattern;
013
014import armyc2.c5isr.JavaLineArray.TacticalLines;
015
016/**
017 * Has various utility functions for prcessing the symbol code.
018 * See {@link SymbolID} for additional functions related to parsing the symbol code.
019*
020 */
021public class SymbolUtilities {
022
023    private static SimpleDateFormat dateFormatFront = new SimpleDateFormat("ddHHmmss", Locale.US);
024    private static SimpleDateFormat dateFormatBack = new SimpleDateFormat("MMMyyyy", Locale.US);
025    private static SimpleDateFormat dateFormatFull = new SimpleDateFormat("ddHHmmssZMMMyyyy", Locale.US);
026    private static SimpleDateFormat dateFormatZulu = new SimpleDateFormat("Z", Locale.US);
027
028    //this regex is from: https://docs.oracle.com/javase/7/docs/api/java/lang/Double.html
029    private static final String Digits     = "(\\p{Digit}+)";
030    private static final String HexDigits  = "(\\p{XDigit}+)";
031    // an exponent is 'e' or 'E' followed by an optionally
032    // signed decimal integer.
033    private static final String Exp        = "[eE][+-]?"+Digits;
034    private static final String fpRegex    =
035            ("[\\x00-\\x20]*"+  // Optional leading "whitespace"
036                    "[+-]?(" + // Optional sign character
037                    "NaN|" +           // "NaN" string
038                    "Infinity|" +      // "Infinity" string
039
040                    // A decimal floating-point string representing a finite positive
041                    // number without a leading sign has at most five basic pieces:
042                    // Digits . Digits ExponentPart FloatTypeSuffix
043                    //
044                    // Since this method allows integer-only strings as input
045                    // in addition to strings of floating-point literals, the
046                    // two sub-patterns below are simplifications of the grammar
047                    // productions from section 3.10.2 of
048                    // The Java™ Language Specification.
049
050                    // Digits ._opt Digits_opt ExponentPart_opt FloatTypeSuffix_opt
051                    "((("+Digits+"(\\.)?("+Digits+"?)("+Exp+")?)|"+
052
053                    // . Digits ExponentPart_opt FloatTypeSuffix_opt
054                    "(\\.("+Digits+")("+Exp+")?)|"+
055
056                    // Hexadecimal strings
057                    "((" +
058                    // 0[xX] HexDigits ._opt BinaryExponent FloatTypeSuffix_opt
059                    "(0[xX]" + HexDigits + "(\\.)?)|" +
060
061                    // 0[xX] HexDigits_opt . HexDigits BinaryExponent FloatTypeSuffix_opt
062                    "(0[xX]" + HexDigits + "?(\\.)" + HexDigits + ")" +
063
064                    ")[pP][+-]?" + Digits + "))" +
065                    "[fFdD]?))" +
066                    "[\\x00-\\x20]*");// Optional trailing "whitespace"
067
068    private static final Pattern pIsNumber = Pattern.compile(fpRegex);
069
070    /**
071     * Determines if a String represents a valid number
072     *
073     * @param text {@link String}
074     * @return "1.56" == true, "1ab" == false
075     */
076    public static boolean isNumber(String text)
077    {
078        return pIsNumber.matcher(text).matches();
079    }
080
081
082    /*private static String convert(int integer)
083    {
084        String hexAlphabet = "0123456789ABCDEF";
085        String foo = "gfds" + "dhs";
086        char char1 =  hexAlphabet.charAt((integer - integer % 16)/16);
087        char char2 = hexAlphabet.charAt(integer % 16);
088        String returnVal = String.valueOf(char1) + String.valueOf(char2);
089        return returnVal;
090    }
091
092    public static String colorToHexString(Color color, Boolean withAlpha)
093    {
094        if(color != null) {
095            String hex = "";
096            if (withAlpha == false) {
097                hex = "#" + convert(color.getRed()) +
098                        convert(color.getGreen()) +
099                        convert(color.getBlue());
100            } else {
101                hex = "#" + convert(color.getAlpha()) +
102                        convert(color.getRed()) +
103                        convert(color.getGreen()) +
104                        convert(color.getBlue());
105            }
106            return hex;
107        }
108        else
109            return null;
110    }//*/
111
112
113    /**
114     * Converts a Java Date object into a properly formatted String for W or W1.
115     * DDHHMMSSZMONYYYY
116     * Field W: D = day, H = hour, M = minute, S = second, Z = Greenwich or local time, MON= month and Y = year.
117     * @param time {@link Date}
118     * @return {@link String}
119     */
120    public static String getDateLabel(Date time)
121    {
122
123        String modifierString = null;
124
125        String zulu = "";
126        zulu = dateFormatZulu.format(time);
127
128        if (zulu != null && zulu.length() == 5)
129        {
130
131            if (zulu.startsWith("+"))//Integer.valueOf doesn't like '+'
132            {
133                zulu = zulu.substring(1, 3);
134            }
135            else
136            {
137                zulu = zulu.substring(0, 3);
138            }
139
140            int intZulu = Integer.valueOf(zulu);
141
142            zulu = getZuluCharFromTimeZoneOffset(intZulu);
143        }
144        else
145        {
146            zulu = getZuluCharFromTimeZoneOffset(time);
147        }
148
149        modifierString = dateFormatFront.format(time) + zulu + dateFormatBack.format(time);
150
151        return modifierString.toUpperCase();
152    }
153
154    /**
155     * Given date, return character String representing which NATO time zone
156     * you're in.
157     *
158     * @param time {@link Date}
159     * @return {@link String}
160     */
161    private static String getZuluCharFromTimeZoneOffset(Date time)
162    {
163        TimeZone tz = TimeZone.getDefault();
164        Date offset = new Date(tz.getOffset(time.getTime()));
165        long lOffset = offset.getTime() / 3600000;//3600000 = (1000(ms)*60(s)*60(m))
166
167        int hour = (int) lOffset;
168
169        return getZuluCharFromTimeZoneOffset(hour);
170    }
171
172    /**
173     * Given hour offset from Zulu return character String representing which
174     * NATO time zone you're in.
175     *
176     * @param hour {@link Integer}
177     * @return {@link String}
178     */
179    private static String getZuluCharFromTimeZoneOffset(int hour)
180    {
181        if (hour == 0)
182        {
183            return "Z";
184        }
185        else if (hour == -1)
186        {
187            return "N";
188        }
189        else if (hour == -2)
190        {
191            return "O";
192        }
193        else if (hour == -3)
194        {
195            return "P";
196        }
197        else if (hour == -4)
198        {
199            return "Q";
200        }
201        else if (hour == -5)
202        {
203            return "R";
204        }
205        else if (hour == -6)
206        {
207            return "S";
208        }
209        else if (hour == -7)
210        {
211            return "T";
212        }
213        else if (hour == -8)
214        {
215            return "U";
216        }
217        else if (hour == -9)
218        {
219            return "V";
220        }
221        else if (hour == -10)
222        {
223            return "W";
224        }
225        else if (hour == -11)
226        {
227            return "X";
228        }
229        else if (hour == -12)
230        {
231            return "Y";
232        }
233        else if (hour == 1)
234        {
235            return "A";
236        }
237        else if (hour == 2)
238        {
239            return "B";
240        }
241        else if (hour == 3)
242        {
243            return "C";
244        }
245        else if (hour == 4)
246        {
247            return "D";
248        }
249        else if (hour == 5)
250        {
251            return "E";
252        }
253        else if (hour == 6)
254        {
255            return "F";
256        }
257        else if (hour == 7)
258        {
259            return "G";
260        }
261        else if (hour == 8)
262        {
263            return "H";
264        }
265        else if (hour == 9)
266        {
267            return "I";
268        }
269        else if (hour == 10)
270        {
271            return "K";
272        }
273        else if (hour == 11)
274        {
275            return "L";
276        }
277        else if (hour == 12)
278        {
279            return "M";
280        }
281        else
282        {
283            return "-";
284        }
285    }
286
287    /**
288     * Determines if a symbol, based on it's symbol ID, can have the specified modifier/amplifier.
289     * @param symbolID 30 Character {@link String}
290     * @param modifier {@link Modifiers}
291     * @return {@link Boolean}
292     */
293    public static Boolean hasModifier(String symbolID, String modifier)
294    {
295        MSInfo msi = MSLookup.getInstance().getMSLInfo(symbolID);
296
297        if(msi != null)//  && msi.getDrawRule() != DrawRules.DONOTDRAW)
298        {
299            ArrayList<String> mods = msi.getModifiers();
300
301            if(mods != null && mods.contains(modifier))
302                return true;
303            else if(msi.getSymbolSet() == SymbolID.SymbolSet_ControlMeasure && modifier.equals(Modifiers.AB_FEINT_DUMMY_INDICATOR))
304                return true;
305            else
306                return false;
307        }
308        return false;
309    }
310
311    /**
312     * Gets Basic Symbol ID which is the Symbol Set + Entity Code
313     * @param id 30 Character {@link String}
314     * @return 8 character {@link String} (Symbol Set + Entity Code)
315     */
316    public static String getBasicSymbolID(String id)
317    {
318        if(id.length() == 8)
319        {
320            return id;
321        }
322        else if(id.startsWith("B"))
323            return id;
324        else if(id.equals("octagon"))
325            return id;
326        else if (id.length() >= 20 && id.length() <= 30)
327        {
328            String key = id.substring(4, 6) + id.substring(10, 16);
329            return key;
330        }
331        else if (id.length()==15)
332        {
333            return getBasicSymbolID2525C(id);
334        }
335        return id;
336    }
337
338    /**
339     * Gets the basic Symbol ID for a 2525C symbol
340     * S*F*GPU---*****
341     * G*G*GPP---****X
342     * @param strSymbolID 15 Character {@link String}
343     * @return 15 Character {@link String}
344     * @deprecated function will be removed
345     */
346    public static String getBasicSymbolID2525C(String strSymbolID)
347    {
348        if(strSymbolID != null && strSymbolID.length() == 15)
349        {
350            StringBuilder sb = new StringBuilder();
351            char scheme = strSymbolID.charAt(0);
352            if (scheme == 'G')
353            {
354                sb.append(strSymbolID.charAt(0));
355                sb.append("*");
356                sb.append(strSymbolID.charAt(2));
357                sb.append("*");
358                sb.append(strSymbolID.substring(4, 10));
359                sb.append("****X");
360            }
361            else if (scheme != 'W' && scheme != 'B' && scheme != 'P')
362            {
363                sb.append(strSymbolID.charAt(0));
364                sb.append("*");
365                sb.append(strSymbolID.charAt(2));
366                sb.append("*");
367                sb.append(strSymbolID.substring(4, 10));
368                sb.append("*****");
369            }
370            else
371            {
372                return strSymbolID;
373            }
374            return sb.toString();
375        }
376        return strSymbolID;
377    }
378
379    /**
380     * Attempts to resolve a bad symbol ID into a value that can be found in {@link MSLookup}.
381     * If it fails, it will return the symbol code for a invalid symbol which is displayed as
382     * an inverted question mark (110098000010000000000000000000)
383     * @param symbolID 30 character {@link String}
384     * @return 30 character {@link String} representing the resolved symbol ID.
385     */
386    public static String reconcileSymbolID(String symbolID)
387    {
388
389        String newID = "";
390        try {
391
392
393            int v = SymbolID.getVersion(symbolID);
394            if (v < SymbolID.Version_2525E)
395                newID = String.valueOf(SymbolID.Version_2525Dch1);
396            else
397                newID = String.valueOf(SymbolID.Version_2525E);
398            int c = SymbolID.getContext(symbolID);
399            if (c > 2)
400                newID += String.valueOf(SymbolID.StandardIdentity_Context_Reality);
401            else
402                newID += String.valueOf(c);
403            int a = SymbolID.getAffiliation(symbolID);
404            if (a > 6)
405                newID += String.valueOf(SymbolID.StandardIdentity_Affiliation_Unknown);
406            else
407                newID += String.valueOf(a);
408            int ss = SymbolID.getSymbolSet(symbolID);
409            switch (ss) {
410                case SymbolID.SymbolSet_Unknown:
411                case SymbolID.SymbolSet_Air:
412                case SymbolID.SymbolSet_AirMissile:
413                case SymbolID.SymbolSet_SignalsIntelligence_Air:
414                case SymbolID.SymbolSet_Space:
415                case SymbolID.SymbolSet_SpaceMissile:
416                case SymbolID.SymbolSet_SignalsIntelligence_Space:
417                case SymbolID.SymbolSet_LandUnit:
418                case SymbolID.SymbolSet_LandCivilianUnit_Organization:
419                case SymbolID.SymbolSet_LandEquipment:
420                case SymbolID.SymbolSet_SignalsIntelligence_Land:
421                case SymbolID.SymbolSet_LandInstallation:
422                case SymbolID.SymbolSet_DismountedIndividuals:
423                case SymbolID.SymbolSet_SeaSurface:
424                case SymbolID.SymbolSet_SignalsIntelligence_SeaSurface:
425                case SymbolID.SymbolSet_SeaSubsurface:
426                case SymbolID.SymbolSet_MineWarfare:
427                case SymbolID.SymbolSet_SignalsIntelligence_SeaSubsurface:
428                case SymbolID.SymbolSet_Activities:
429                case SymbolID.SymbolSet_ControlMeasure:
430                case SymbolID.SymbolSet_Atmospheric:
431                case SymbolID.SymbolSet_Oceanographic:
432                case SymbolID.SymbolSet_MeteorologicalSpace:
433                case SymbolID.SymbolSet_CyberSpace:
434                    newID += String.format("%02d",ss);
435                    break;
436                default:
437                    newID += String.format("%02d", SymbolID.SymbolSet_Unknown);//String.valueOf(SymbolID.SymbolSet_Unknown);
438            }
439
440            int s = SymbolID.getStatus(symbolID);
441            if (s > SymbolID.Status_Present_FullToCapacity)
442                newID += String.valueOf(SymbolID.Status_Present);
443            else
444                newID += String.valueOf(s);
445
446            newID += String.valueOf(SymbolID.getHQTFD(symbolID));//just add, won't get used if value bad
447            newID += String.format("%02d",SymbolID.getAmplifierDescriptor(symbolID));//just add, won't get used if value bad
448
449            int ec = SymbolID.getEntityCode(symbolID);
450
451            if (ec == 0)
452                newID += "000000";//root symbol for symbol set
453            else if (SVGLookup.getInstance().getSVGLInfo(SVGLookup.getMainIconID(newID + ec + "0000"), v) == null) {
454                //set to invalid symbol since we couldn't find it in the lookup
455                newID = SymbolID.setSymbolSet(newID, 98);
456                newID += 100000;
457            }
458            else
459                newID += String.format("%06d",ec);//we found it so add the entity code
460
461            //newID += SymbolID.getMod1ID(symbolID);//just add, won't get used if value bad
462            //newID += SymbolID.getMod2ID(symbolID);//just add, won't get used if value bad
463            newID += symbolID.substring(16);//just add, won't get used if value bad
464        }
465        catch(Exception exc)
466        {
467            newID = "110098000010000000000000000000";//invalid symbol
468        }
469
470        return newID;
471    }
472
473    /**
474     * Gets line color used if no line color has been set. The color is specified based on the affiliation of
475     * the symbol and whether it is a unit or not.
476     * @param symbolID 30 character {@link String}
477     * @return {@link Color}
478     */
479    public static Color getLineColorOfAffiliation(String symbolID)
480    {
481        Color retColor = null;
482
483        int symbolSet = SymbolID.getSymbolSet(symbolID);
484        int set = SymbolID.getSymbolSet(symbolID);
485        int affiliation = SymbolID.getAffiliation(symbolID);
486        int symStd = SymbolID.getVersion(symbolID);
487        int entityCode = SymbolID.getEntityCode(symbolID);
488
489        try
490        {
491            // We can't get the line color if there is no symbol id, since that also means there is no affiliation
492            if((symbolID == null) || (symbolID.equals("")))
493            {
494                return retColor;
495            }
496
497            if(symbolSet == SymbolID.SymbolSet_ControlMeasure)
498            {
499                int entity = SymbolID.getEntity(symbolID);
500                int entityType = SymbolID.getEntityType(symbolID);
501                int entitySubtype = SymbolID.getEntitySubtype(symbolID);
502
503                if(SymbolUtilities.isGreenProtectionGraphic(entity, entityType, entitySubtype))
504                {
505                    //Obstacles/Protection Graphics, some are green obstacles and we need to
506                    //check for those.
507                    retColor = AffiliationColors.ObstacleGreen;
508                }
509                //just do color by affiliation if no other color has been set yet.
510                if(retColor == null)
511                {
512                    switch (affiliation) {
513                        case SymbolID.StandardIdentity_Affiliation_Friend:
514                        case SymbolID.StandardIdentity_Affiliation_AssumedFriend:
515                            retColor = AffiliationColors.FriendlyGraphicLineColor;//Color.BLACK;//0x000000;     // Black
516                            break;
517                        case SymbolID.StandardIdentity_Affiliation_Hostile_Faker:
518                            retColor = AffiliationColors.HostileGraphicLineColor;//Color.RED;//0xff0000;        // Red
519                            break;
520                        case SymbolID.StandardIdentity_Affiliation_Suspect_Joker:
521                            if(symStd >= SymbolID.Version_2525E)
522                                retColor = AffiliationColors.SuspectGraphicLineColor;//255,188,1
523                            else
524                                retColor = AffiliationColors.HostileGraphicLineColor;//Color.RED;//0xff0000;    // Red
525                            break;
526                        case SymbolID.StandardIdentity_Affiliation_Neutral:
527                            retColor = AffiliationColors.NeutralGraphicLineColor;//Color.GREEN;//0x00ff00;      // Green
528                            break;
529                        default:
530                            retColor = AffiliationColors.UnknownGraphicLineColor;//Color.YELLOW;//0xffff00;     // Yellow
531                            break;
532                    }
533                }
534            }
535            else if (set >= 45 && set <= 47)//METOC
536            {
537                // If not black then color will be set in clsMETOC.SetMeTOCProperties()
538                retColor = Color.BLACK;
539            }
540            else if (set == SymbolID.SymbolSet_MineWarfare && (RendererSettings.getInstance().getSeaMineRenderMethod() == RendererSettings.SeaMineRenderMethod_MEDAL))
541            {
542                if(!(entityCode == 110600 || entityCode == 110700))
543                {
544                    switch(affiliation)
545                    {
546                        case SymbolID.StandardIdentity_Affiliation_Friend:
547                        case SymbolID.StandardIdentity_Affiliation_AssumedFriend:
548                            retColor = AffiliationColors.FriendlyUnitFillColor;//0x00ffff;      // Cyan
549                            break;
550                        case SymbolID.StandardIdentity_Affiliation_Hostile_Faker:
551                            retColor = AffiliationColors.HostileGraphicLineColor;//Color.RED;//0xff0000;        // Red
552                            break;
553                        case SymbolID.StandardIdentity_Affiliation_Suspect_Joker:
554                            if(symStd >= SymbolID.Version_2525E)
555                                retColor = AffiliationColors.SuspectGraphicLineColor;//255,188,1
556                            else
557                                retColor = AffiliationColors.HostileGraphicLineColor;//Color.RED;//0xff0000;    // Red
558                            break;
559                        case SymbolID.StandardIdentity_Affiliation_Neutral:
560                            retColor = AffiliationColors.NeutralUnitFillColor;//0x7fff00;       // Light Green
561                            break;
562                        default://unknown, pending, everything else
563                            retColor = AffiliationColors.UnknownUnitFillColor;//new Color(255,250, 205); //0xfffacd;    // LemonChiffon 255 250 205
564                            break;
565                    }
566                }
567                else
568                {
569                    retColor = Color.BLACK;
570                }
571            }
572            else//everything else
573            {
574                //stopped doing check because all warfighting
575                //should have black for line color.
576                retColor = Color.BLACK;
577            }
578        }
579        catch(Exception e)
580        {
581            // Log Error
582            ErrorLogger.LogException("SymbolUtilities", "getLineColorOfAffiliation", e);
583            //throw e;
584        }       // End catch
585        return retColor;
586    }   // End get LineColorOfAffiliation
587
588    /**
589     * For Control Measures, returns the default color for a symbol when it differs from the
590     * affiliation line color.  If there is no default color, returns the value from {@link #getLineColorOfAffiliation}
591     * @param symbolID 30 Character {@link String}
592     * @return {@link Color}
593     */
594    public static Color getDefaultLineColor(String symbolID) {
595        try {
596            if (symbolID == null || symbolID.equals("")) {
597                return null;
598            }
599
600            int symbolSet = SymbolID.getSymbolSet(symbolID);
601            int entityCode = SymbolID.getEntityCode(symbolID);
602            int version = SymbolID.getVersion(symbolID);
603
604            if (symbolSet == SymbolID.SymbolSet_ControlMeasure) {
605                if (entityCode == 200600) {
606                    return Color.WHITE;
607                } else if (entityCode == 200700) {
608                    return new Color(51, 136, 136);
609                } else if (entityCode == 200101) {
610                    return new Color(255, 155, 0);
611                } else if (entityCode == 200201 || entityCode == 200202) {
612                    return new Color(85, 119, 136);
613                } else if (version >= SymbolID.Version_2525E &&
614                        (entityCode == 132100 || //key terrain
615                                entityCode == 282001 || //Tower, Low
616                                entityCode == 282002 || //Tower, High
617                                entityCode == 282003)) { // Overhead Wire
618                    return new Color(128, 0, 128);//purple
619                }
620            }
621        } catch (Exception e) {
622            ErrorLogger.LogException("SymbolUtilities", "getDefaultLineColor", e);
623        }
624        return getLineColorOfAffiliation(symbolID);
625    }
626
627    /**
628     * Checks if a symbol should be filled by default
629     * 
630     * @param strSymbolID The 20 digit representation of the 2525D symbol
631     * @return true if there is a default fill
632     */
633    public static boolean hasDefaultFill(String strSymbolID) {
634        int ec = SymbolID.getEntityCode(strSymbolID);
635        switch (ec) {
636            case 200101:
637            case 200201:
638            case 200202:
639            case 200600:
640            case 200700:
641               return true;
642            default:
643                return !SymbolUtilities.isTacticalGraphic(strSymbolID);
644        }
645    }
646
647    /**
648     * Determines if the symbol is a tactical graphic
649     *
650     * @param strSymbolID 30 Character {@link String}
651     * @return true if symbol set is 25 (control measure), or is a weather graphic
652     */
653    public static boolean isTacticalGraphic(String strSymbolID) {
654        try {
655            int ss = SymbolID.getSymbolSet(strSymbolID);
656
657            if(ss == SymbolID.SymbolSet_ControlMeasure || isWeather(strSymbolID)) {
658                return true;
659            }
660        }
661        catch (Exception e) {
662            ErrorLogger.LogException("SymbolUtilities", "getFillColorOfAffiliation", e);
663        }
664        return false;
665    }
666
667    /**
668     * Determines if the Symbol can be rendered as a multipoint graphic and not just as an icon
669     * @param symbolID 30 Character {@link String}
670     * @return {@link Boolean}
671     */
672    public static boolean isMultiPoint(String symbolID)
673    {
674        MSInfo msi = MSLookup.getInstance().getMSLInfo(symbolID);
675        if (msi == null) {
676            return false;
677        }
678        int drawRule = msi.getDrawRule();
679        int ss = msi.getSymbolSet();
680        if(ss != SymbolID.SymbolSet_ControlMeasure && ss != SymbolID.SymbolSet_Oceanographic && ss != SymbolID.SymbolSet_Atmospheric && ss != SymbolID.SymbolSet_MeteorologicalSpace)
681        {
682            return false;
683        }
684        else if (ss == SymbolID.SymbolSet_ControlMeasure)
685        {
686            if(msi.getMaxPointCount() > 1)
687                return true;
688            else if((drawRule < DrawRules.POINT1 || drawRule > DrawRules.POINT16 || drawRule == DrawRules.POINT12) &&
689                    drawRule != DrawRules.DONOTDRAW && drawRule != DrawRules.AREA22)
690            {
691                return true;
692            }
693            else
694                return false;
695        }
696        else if(ss == SymbolID.SymbolSet_Oceanographic || ss == SymbolID.SymbolSet_Atmospheric || ss == SymbolID.SymbolSet_MeteorologicalSpace)
697        {
698            if(msi.getMaxPointCount() > 1)
699                return true;
700            else
701                return false;
702        }
703        return false;
704    }
705
706    public static boolean isActionPoint(String symbolID)
707    {
708        MSInfo msi = MSLookup.getInstance().getMSLInfo(symbolID);
709        if(msi.getDrawRule()==DrawRules.POINT1)
710        {
711            int ec = SymbolID.getEntityCode(symbolID);
712            if(ec != 131300 && ec != 131301 && ec != 182600 && ec != 212800)
713                return true;
714        }
715        return false;
716    }
717
718    /**
719     * Control Measures and Tactical Graphics that have labels but not with the Action Point layout
720     * @param strSymbolID 30 Character {@link String}
721     * @return {@link Boolean}
722     + @deprecated see {@link #isSPWithSpecialModifierLayout(String)}
723     */
724    public static boolean isTGSPWithSpecialModifierLayout(String strSymbolID)
725    {
726        try
727        {
728            int ss = SymbolID.getSymbolSet(strSymbolID);
729            int entityCode = SymbolID.getEntityCode(strSymbolID);
730            if(ss == SymbolID.SymbolSet_ControlMeasure) //|| isWeather(strSymbolID)) {
731            {
732                if(SymbolUtilities.isCBRNEvent(strSymbolID))
733                    return true;
734
735                if(SymbolUtilities.isSonobuoy(strSymbolID))
736                    return true;
737
738                switch (entityCode)
739                {
740                    case 130500: //contact point
741                    case 130700: //decision point
742                    case 212800: //harbor
743                    case 210300: //Defended Asset
744                    case 210600: //Air Detonation
745                    case 131300: //point of interest
746                    case 131800: //waypoint
747                    case 240900: //fire support station
748                    case 180100: //Air Control point
749                    case 180200: //Communications Check point
750                    case 160300: //T (target reference point)
751                    case 240601: //ap,ap1,x,h (Point/Single Target)
752                    case 240602: //ap (nuclear target)
753                    case 270701: //static depiction
754                    case 282001: //tower, low
755                    case 282002: //tower, high
756                        return true;
757                    default:
758                        return false;
759                }
760            }
761            else if(ss == SymbolID.SymbolSet_Atmospheric)
762            {
763                switch (entityCode)
764                {
765                    case 162300: //Freezing Level
766                    case 162200: //tropopause Level
767                    case 110102: //tropopause Low
768                    case 110202: //tropopause High
769                        return true;
770                    default:
771                        return false;
772                }
773            }
774        }
775        catch (Exception e) {
776            ErrorLogger.LogException("SymbolUtilities", "getFillColorOfAffiliation", e);
777        }
778        return false;
779    }
780
781    /**
782     * Returns the fill color for the symbol based on its affiliation
783     * @param symbolID 30 Character {@link String}
784     * @return {@link Color}
785     */
786    public static Color getFillColorOfAffiliation(String symbolID)
787    {
788        Color retColor = null;
789        int entityCode = SymbolID.getEntityCode(symbolID);
790        int entity = SymbolID.getEntity(symbolID);
791        int entityType = SymbolID.getEntityType(symbolID);
792        int entitySubtype = SymbolID.getEntitySubtype(symbolID);
793
794        int affiliation = SymbolID.getAffiliation(symbolID);
795
796        try
797        {
798            // We can't get the fill color if there is no symbol id, since that also means there is no affiliation
799            if ((symbolID == null) || (symbolID.equals(""))) {
800                return retColor;
801            }
802            if (SymbolID.getSymbolSet(symbolID) == SymbolID.SymbolSet_ControlMeasure) {
803                switch (entityCode) {
804                    case 200101:
805                        retColor = new Color(255, 155, 0, (int) (.25 * 255));
806                        break;
807                    case 200201:
808                    case 200202:
809                    case 200600:
810                        retColor = new Color(85, 119, 136, (int) (.25 * 255));
811                        break;
812                    case 200700:
813                        retColor = new Color(51, 136, 136, (int) (.25 * 255));
814                        break;
815                }
816            }
817            else if (SymbolID.getSymbolSet(symbolID) == SymbolID.SymbolSet_MineWarfare &&
818                    (RendererSettings.getInstance().getSeaMineRenderMethod() == RendererSettings.SeaMineRenderMethod_MEDAL) &&
819                    (!(entityCode == 110600 || entityCode == 110700)))
820            {
821                retColor = new Color(0,0,0,0);//transparent
822            }
823            //just do color by affiliation if no other color has been set yet
824            if (retColor == null) {
825                switch(affiliation)
826                {
827                    case SymbolID.StandardIdentity_Affiliation_Friend:
828                    case SymbolID.StandardIdentity_Affiliation_AssumedFriend:
829                        retColor = AffiliationColors.FriendlyUnitFillColor;//0x00ffff;  // Cyan
830                        break;
831                    case SymbolID.StandardIdentity_Affiliation_Hostile_Faker:
832                        retColor = AffiliationColors.HostileUnitFillColor;//0xfa8072;   // Salmon
833                        break;
834                    case SymbolID.StandardIdentity_Affiliation_Suspect_Joker:
835                        if(SymbolID.getVersion(symbolID) >= SymbolID.Version_2525E)
836                            retColor = AffiliationColors.SuspectGraphicFillColor;//255,229,153
837                        else
838                            retColor = AffiliationColors.HostileGraphicFillColor;//Color.RED;//0xff0000;        // Red
839                        break;
840                    case SymbolID.StandardIdentity_Affiliation_Neutral:
841                        retColor = AffiliationColors.NeutralUnitFillColor;//0x7fff00;   // Light Green
842                        break;
843                    default://unknown, pending, everything else
844                        retColor = AffiliationColors.UnknownUnitFillColor;//new Color(255,250, 205); //0xfffacd;        // LemonChiffon 255 250 205
845                        break;
846                }
847            }
848        } // End try
849        catch (Exception e)
850        {
851            // Log Error
852            ErrorLogger.LogException("SymbolUtilities", "getFillColorOfAffiliation", e);
853            //throw e;
854        }       // End catch
855
856        return retColor;
857    }   // End FillColorOfAffiliation
858
859    /**
860     *
861     * @param symbolID 30 Character {@link String}
862     * @param modifier {@link Modifiers} 
863     * @return {@link Boolean}
864     * @deprecated see {@link #hasModifier(String, String)}
865     */
866    public static Boolean canSymbolHaveModifier(String symbolID, String modifier)
867    {
868        return hasModifier(symbolID, modifier);
869    }
870
871    /**
872     * Checks if the Symbol Code has FDI set.
873     * Does not check if the symbol can have an FDI.
874     * @param symbolID 30 Character {@link String}
875     * @return {@link Boolean}
876     */
877    public static Boolean hasFDI(String symbolID)
878    {
879        int hqtfd = SymbolID.getHQTFD(symbolID);
880        if(hqtfd == SymbolID.HQTFD_FeintDummy
881                || hqtfd == SymbolID.HQTFD_FeintDummy_TaskForce
882                || hqtfd == SymbolID.HQTFD_FeintDummy_Headquarters
883                || hqtfd == SymbolID.HQTFD_FeintDummy_TaskForce_Headquarters)
884        {
885            return true;
886        }
887        else
888            return false;
889    }
890
891    /*
892     * For Renderer Use Only
893     * Assumes a fresh SVG String from the SVGLookup with its default values
894     * @param symbolID
895     * @param svg
896     * @param strokeColor
897     * @param fillColor
898     * @return
899     */
900    /*public static String setSVGFrameColors(String symbolID, String svg, Color strokeColor, Color fillColor)
901    {
902        String hexStrokeColor = null;
903        String hexFillColor = null;
904
905        if(strokeColor != null)
906            hexStrokeColor = colorToHexString(strokeColor,false);
907        if(fillColor != null)
908            hexFillColor = colorToHexString(fillColor,false);
909        return setSVGFrameColors(symbolID, svg, hexStrokeColor,hexFillColor);
910    }//*/
911
912    /***
913     * Returns true if graphic is protection graphic (obstacles which render green)
914     * Assumes control measure symbol code where SS == 25
915     * @param entity {@link Integer}
916     * @param entityType {@link Integer}
917     * @param entitySubtype {@link Integer}
918     * @return {@link Boolean}
919     */
920    public static boolean isGreenProtectionGraphic(int entity, int entityType, int entitySubtype)
921    {
922        if(entity >= 27 && entity <= 29)//Protection Areas, Points and Lines
923        {
924            if(entity == 27)
925            {
926                if(entityType > 0 && entityType <= 5)
927                    return true;
928                else if(entityType == 7 || entityType == 8 || entityType == 10 || entityType == 12)
929                {
930                    return true;
931                }
932                else
933                    return false;
934            }
935            else if(entity == 28)
936            {
937                if(entityType > 0 && entityType <= 7)
938                    return true;
939                if(entityType == 19)
940                    return true;
941                else
942                    return false;
943            }
944            else if(entity == 29)
945            {
946                if(entityType >= 01 && entityType <= 05)
947                    return true;
948                else
949                    return false;
950            }
951        }
952        else
953        {
954            return false;
955        }
956        return false;
957    }
958
959    /**
960     * Returns true if graphic is protection graphic (obstacles which render green)
961     * @param symbolID 30 Character {@link String}
962     * @return {@link Boolean}
963     */
964    public static boolean isGreenProtectionGraphic(String symbolID){
965        if (SymbolID.getSymbolSet(symbolID) == SymbolID.SymbolSet_ControlMeasure) {
966            return SymbolUtilities.isGreenProtectionGraphic(SymbolID.getEntity(symbolID), SymbolID.getEntityType(symbolID), SymbolID.getEntitySubtype(symbolID));
967        } else {
968            return false;
969        }
970    }
971
972    /**
973     * Returns true if Symbol ID represents a chemical, biological, radiological or nuclear incident.
974     * @param symbolID 30 Character {@link String}
975     * @return {@link Boolean}
976     */
977    public static boolean isCBRNEvent(String symbolID)
978    {
979        int ss = SymbolID.getSymbolSet(symbolID);
980        int ec = SymbolID.getEntityCode(symbolID);
981
982        if(ss == SymbolID.SymbolSet_ControlMeasure) {
983            switch (ec)
984            {
985                case 281300:
986                case 281400:
987                case 281500:
988                case 281600:
989                case 281700:
990                    return true;
991                default:
992            }
993        }
994        return false;
995    }
996
997    /**
998     * Returns true if Symbol ID represents a Sonobuoy.
999     * @param symbolID 30 Character {@link String}
1000     * @return {@link Boolean}
1001     */
1002    public static boolean isSonobuoy(String symbolID)
1003    {
1004        int ss = SymbolID.getSymbolSet(symbolID);
1005        int e = SymbolID.getEntity(symbolID);
1006        int et = SymbolID.getEntityType(symbolID);
1007        if(ss == 25 && e == 21 && et == 35)
1008            return true;
1009        else
1010            return false;
1011    }
1012
1013    /**
1014     * Obstacles are generally required to have a green line color
1015     * @param symbolID 30 Character {@link String}
1016     * @return {@link Boolean}
1017     * @deprecated see {@link #isGreenProtectionGraphic(String)}
1018     */
1019    public static boolean isObstacle(String symbolID)
1020    {
1021
1022        if(SymbolID.getSymbolSet(symbolID) == SymbolID.SymbolSet_ControlMeasure &&
1023                SymbolID.getEntity(symbolID) == 27)
1024        {
1025            return true;
1026        }
1027        else
1028            return false;
1029    }
1030
1031    /**
1032     * Return true if symbol is from the Atmospheric, Oceanographic or Meteorological Space Symbol Sets.
1033     * @param symbolID 30 Character {@link String}
1034     * @return {@link Boolean}
1035     */
1036    public static boolean isWeather(String symbolID)
1037    {
1038        int ss = SymbolID.getSymbolSet(symbolID);
1039        if(ss >= SymbolID.SymbolSet_Atmospheric && ss <= SymbolID.SymbolSet_MeteorologicalSpace)
1040            return true;
1041        else
1042            return false;
1043    }
1044
1045    /**
1046     * Returns true if the symbol has the HQ staff indicated by the symbol ID
1047     * @param symbolID 30 Character {@link String}
1048     * @return {@link Boolean}
1049     */
1050    public static boolean isHQ(String symbolID)
1051    {
1052        int hq = SymbolID.getHQTFD(symbolID);
1053        if(SymbolUtilities.hasModifier(symbolID, Modifiers.S_HQ_STAFF_INDICATOR) &&
1054                (hq == SymbolID.HQTFD_FeintDummy_Headquarters ||
1055                        hq == SymbolID.HQTFD_Headquarters  ||
1056                        hq == SymbolID.HQTFD_FeintDummy_TaskForce_Headquarters ||
1057                        hq == SymbolID.HQTFD_TaskForce_Headquarters))
1058            return true;
1059        else
1060            return false;
1061    }
1062
1063    /**
1064     * Checks if this is a single point control measure  or meteorological graphic with a unique layout.
1065     * Basically anything that's not an action point style graphic with modifiers
1066     * @param symbolID 30 Character {@link String}
1067     * @return {@link Boolean}
1068     */
1069    public static boolean isSPWithSpecialModifierLayout(String symbolID)
1070    {
1071        int ss = SymbolID.getSymbolSet(symbolID);
1072        int ec = SymbolID.getEntityCode(symbolID);
1073
1074        if(ss == SymbolID.SymbolSet_ControlMeasure)
1075        {
1076            switch(ec)
1077            {
1078                case 130500: //Control Point
1079                case 130700: //Decision Point
1080                case 131300: //Point of Interest
1081                case 131800: //Waypoint
1082                case 131900: //Airfield (AEGIS Only)
1083                case 132000: //Target Handover
1084                case 132100: //Key Terrain
1085                case 160300: //Target Point Reference
1086                case 180100: //Air Control Point
1087                case 180200: //Communications Check Point
1088                case 180600: //TACAN
1089                case 210300: //Defended Asset
1090                case 210600: //Air Detonation
1091                case 210800: //Impact Point
1092                case 211000: //Launched Torpedo
1093                case 212800: //Harbor
1094                case 213500: //Sonobuoy
1095                case 213501: //Ambient Noise Sonobuoy
1096                case 213502: //Air Transportable Communication (ATAC) (Sonobuoy)
1097                case 213503: //Barra (Sonobuoy)
1098                case 213504:
1099                case 213505:
1100                case 213506:
1101                case 213507:
1102                case 213508:
1103                case 213509:
1104                case 213510:
1105                case 213511:
1106                case 213512:
1107                case 213513:
1108                case 213514:
1109                case 213515:
1110                case 214900: //General Sea Subsurface Station
1111                case 215600: //General Sea Station
1112                case 217000: //Shore Control Station
1113                case 240601: //Point or Single Target
1114                case 240602: //Nuclear Target
1115                case 240900: //Fire Support Station
1116                case 250600: //Known Point
1117                case 270701: //Static Depiction
1118                case 282001: //Tower, Low
1119                case 282002: //Tower, High
1120                case 281300: //Chemical Event
1121                case 281400: //Biological Event
1122                case 281500: //Nuclear Event
1123                case 281600: //Nuclear Fallout Producing Event
1124                case 281700: //Radiological Event
1125                    return true;
1126                default:
1127                    return false;
1128            }
1129        }
1130        else if(ss == SymbolID.SymbolSet_Atmospheric)
1131        {
1132            switch(ec)
1133            {
1134                case 162300: //Freezing Level
1135                case 162200: //tropopause Level
1136                case 110102: //tropopause low
1137                case 110202: //tropopause high
1138                    return true;
1139                default:
1140                    return false;
1141            }
1142        }
1143        return false;
1144    }
1145
1146    /**
1147     * Gets the anchor point for single point Control Measure as the anchor point isn't always they center of the symbol.
1148     * @param symbolID 30 Character {@link String}
1149     * @param bounds {@link RectF} representing the bound of the core symbol in the image.
1150     * @return {@link Point} representing the point in the image that is the anchor point of the symbol.
1151     */
1152    public static Point getCMSymbolAnchorPoint(String symbolID, RectF bounds) {
1153        float centerX = bounds.width() / 2f;
1154        float centerY = bounds.height() / 2f;
1155
1156        int ss = SymbolID.getSymbolSet(symbolID);
1157        int ec = SymbolID.getEntityCode(symbolID);
1158        int drawRule = 0;
1159
1160        //center/anchor point is always half width and half height except for control measures
1161        //and meteorological
1162        if (ss == SymbolID.SymbolSet_ControlMeasure) {
1163            drawRule = MSLookup.getInstance().getMSLInfo(symbolID).getDrawRule();
1164            switch (drawRule)//here we check the 'Y' value for the anchor point
1165            {
1166                case DrawRules.POINT1://action points 1301 //bottom center
1167                case DrawRules.POINT5://entry point 2105
1168                case DrawRules.POINT6://ground zero 2107
1169                case DrawRules.POINT7://missile detection point 2111
1170                    centerY = bounds.height();
1171                    break;
1172                case DrawRules.POINT4://drop point 2104 //almost bottom and center
1173                    centerY = (bounds.height() * 0.80f);
1174                    break;
1175                case DrawRules.POINT10://Sonobuoy 2135 //center of circle which isn't center of symbol
1176                    centerY = (bounds.height() * 0.75f);
1177                    break;
1178                case DrawRules.POINT13://booby trap 2807 //almost bottom and center
1179                    centerY = (bounds.height() * 0.74f);
1180                    break;
1181                case DrawRules.POINT15://Marine Life 2189 //center left
1182                    centerX = 0;
1183                    break;
1184                case DrawRules.POINT16://Tower 282001 //circle at base of tower
1185                    centerY = (bounds.height() * 0.87f);
1186                    break;
1187                case DrawRules.POINT2://Several different symbols
1188                    if (ec == 280500)//Wide Area Antitank Mine
1189                        centerY = (bounds.height() * 0.35f);
1190                    else if (ec == 280400)//Antitank Mine w/ Anti-handling Device
1191                        centerY = (bounds.height() * 0.33f);
1192                    else if (ec == 280200)//Antipersonnel Mine
1193                        centerY = (bounds.height() * 0.7f);
1194                    else if (ec == 280201)//Antipersonnel Mine with Directional Effects
1195                        centerY = (bounds.height() * 0.65f);
1196                    else if (ec == 219000)//Sea Anomaly
1197                        centerY = (bounds.height() * 0.7f);
1198                    else if (ec == 212500)//Electromagnetic - Magnetic Anomaly Detections (MAD)
1199                        centerY = (bounds.height() * 0.4f);
1200                    break;
1201                default:
1202            }
1203
1204            switch (ec)
1205            //have to adjust center X as some graphics have integrated text outside the symbol
1206            {
1207                case 180400: //Pickup Point (PUP)
1208                    centerX = bounds.width() * 0.3341f;
1209                    break;
1210                case 240900: //Fire Support Station
1211                    centerX = bounds.width() * 0.38f;
1212                    break;
1213                case 280201: //Antipersonnel Mine with Directional Effects
1214                    centerX = bounds.width() * 0.43f;
1215                    break;
1216            }
1217        }
1218
1219        return new Point(Math.round(centerX), Math.round(centerY));
1220    }
1221
1222    /**
1223     * Returns true if the symbol is an installation
1224     * @param symbolID 30 Character {@link String}
1225     * @return {@link Boolean}
1226     */
1227    public static Boolean isInstallation(String symbolID)
1228    {
1229        int ss = SymbolID.getSymbolSet(symbolID);
1230        int entity = SymbolID.getEntity(symbolID);
1231        if(ss == SymbolID.SymbolSet_LandInstallation && entity == 11)
1232            return true;
1233        else
1234            return false;
1235    }
1236
1237    /**
1238     * Returns true if the symbol is from an air based symbol set
1239     * @param symbolID 30 Character {@link String}
1240     * @return {@link Boolean}
1241     */
1242    public static Boolean isAir(String symbolID)
1243    {
1244        int ss = SymbolID.getSymbolSet(symbolID);
1245        int entity = SymbolID.getEntity(symbolID);
1246        if(ss == SymbolID.SymbolSet_Air ||
1247                ss == SymbolID.SymbolSet_AirMissile ||
1248                ss == SymbolID.SymbolSet_SignalsIntelligence_Air)
1249            return true;
1250        else
1251            return false;
1252    }
1253
1254    /**
1255     * Returns true if the symbol is from a space based symbol set
1256     * @param symbolID 30 Character {@link String}
1257     * @return {@link Boolean}
1258     */
1259    public static Boolean isSpace(String symbolID)
1260    {
1261        int ss = SymbolID.getSymbolSet(symbolID);
1262        int entity = SymbolID.getEntity(symbolID);
1263        if(ss == SymbolID.SymbolSet_Space ||
1264                ss == SymbolID.SymbolSet_SpaceMissile ||
1265                ss == SymbolID.SymbolSet_SignalsIntelligence_Space)
1266            return true;
1267        else
1268            return false;
1269    }
1270
1271    /**
1272     * Returns true if the symbol is from a land based symbol set
1273     * @param symbolID 30 Character {@link String}
1274     * @return {@link Boolean}
1275     */
1276    public static Boolean isLand(String symbolID)
1277    {
1278        int ss = SymbolID.getSymbolSet(symbolID);
1279        int entity = SymbolID.getEntity(symbolID);
1280        if(ss == SymbolID.SymbolSet_LandUnit ||
1281                ss == SymbolID.SymbolSet_LandCivilianUnit_Organization ||
1282                ss == SymbolID.SymbolSet_LandEquipment ||
1283                ss == SymbolID.SymbolSet_LandInstallation ||
1284                ss == SymbolID.SymbolSet_SignalsIntelligence_Land)
1285            return true;
1286        else
1287            return false;
1288    }
1289
1290    /**
1291     * Returns true if the symbol ID has the task for indicator
1292     * @param symbolID 30 Character {@link String}
1293     * @return {@link Boolean}
1294     */
1295    public static Boolean isTaskForce(String symbolID)
1296    {
1297        int hqtfd = SymbolID.getHQTFD(symbolID);
1298        if((hqtfd == SymbolID.HQTFD_TaskForce ||
1299                hqtfd == SymbolID.HQTFD_TaskForce_Headquarters ||
1300                hqtfd == SymbolID.HQTFD_FeintDummy_TaskForce ||
1301                hqtfd == SymbolID.HQTFD_FeintDummy_TaskForce_Headquarters) &&
1302                SymbolUtilities.canSymbolHaveModifier(symbolID, Modifiers.B_ECHELON))
1303            return true;
1304        else
1305            return false;
1306    }
1307
1308    /**
1309     * Returns true if the symbol ID indicates the context is Reality
1310     * @param symbolID 30 Character {@link String}
1311     * @return {@link Boolean}
1312     */
1313    public static Boolean isReality(String symbolID)
1314    {
1315        int c = SymbolID.getContext(symbolID);
1316        if(c == SymbolID.StandardIdentity_Context_Reality ||
1317                c == 3 || c == 4)
1318            return true;
1319        else
1320            return false;
1321    }
1322
1323    /**
1324     * Returns true if the symbol ID indicates the context is Exercise
1325     * @param symbolID 30 Character {@link String}
1326     * @return {@link Boolean}
1327     */
1328    public static Boolean isExercise(String symbolID)
1329    {
1330        int c = SymbolID.getContext(symbolID);
1331        if(c == SymbolID.StandardIdentity_Context_Exercise ||
1332                c == 5 || c == 6)
1333            return true;
1334        else
1335            return false;
1336    }
1337
1338    /**
1339     * Returns true if the symbol ID indicates the context is Simulation
1340     * @param symbolID 30 Character {@link String}
1341     * @return {@link Boolean}
1342     */
1343    public static Boolean isSimulation(String symbolID)
1344    {
1345        int c = SymbolID.getContext(symbolID);
1346        if(c == SymbolID.StandardIdentity_Context_Simulation ||
1347                c == 7 || c == 8)
1348            return true;
1349        else
1350            return false;
1351    }
1352
1353
1354    /**
1355     * Reads the Symbol ID string and returns the text that represents the echelon
1356     * code.
1357     * @param echelon {@link Integer} from positions 9-10 in the symbol ID
1358     * See {@link SymbolID#getAmplifierDescriptor(String)}
1359     * @return {@link String} (23 (Army) would be "XXXX")
1360     */
1361    public static String getEchelonText(int echelon)
1362    {
1363        char[] dots = new char[3];
1364        dots[0] = (char)8226;
1365        dots[1] = (char)8226;
1366        dots[2] = (char)8226;
1367        String dot = new String(dots);
1368        String text = null;
1369        if(echelon == SymbolID.Echelon_Team_Crew)
1370        {
1371            text = (char) 216 + "";
1372        }
1373        else if(echelon == SymbolID.Echelon_Squad)
1374        {
1375            text = dot.substring(0, 1);
1376        }
1377        else if(echelon == SymbolID.Echelon_Section)
1378        {
1379            text = dot.substring(0, 2);
1380        }
1381        else if(echelon == SymbolID.Echelon_Platoon_Detachment)
1382        {
1383            text = dot;
1384        }
1385        else if(echelon == SymbolID.Echelon_Company_Battery_Troop)
1386        {
1387            text = "I";
1388        }
1389        else if(echelon == SymbolID.Echelon_Battalion_Squadron)
1390        {
1391            text = "II";
1392        }
1393        else if(echelon == SymbolID.Echelon_Regiment_Group)
1394        {
1395            text = "III";
1396        }
1397        else if(echelon == SymbolID.Echelon_Brigade)
1398        {
1399            text = "X";
1400        }
1401        else if(echelon == SymbolID.Echelon_Division)
1402        {
1403            text = "XX";
1404        }
1405        else if(echelon == SymbolID.Echelon_Corps_MEF)
1406        {
1407            text = "XXX";
1408        }
1409        else if(echelon == SymbolID.Echelon_Army)
1410        {
1411            text = "XXXX";
1412        }
1413        else if(echelon == SymbolID.Echelon_ArmyGroup_Front)
1414        {
1415            text = "XXXXX";
1416        }
1417        else if(echelon == SymbolID.Echelon_Region_Theater)
1418        {
1419            text = "XXXXXX";
1420        }
1421        else if(echelon == SymbolID.Echelon_Region_Command)
1422        {
1423            text = "++";
1424        }
1425        return text;
1426    }
1427
1428    /**
1429     * Returns the Standard Identity Modifier based on the Symbol ID
1430     * @param symbolID 30 Character {@link String}
1431     * @return {@link String}
1432     */
1433    public static String getStandardIdentityModifier(String symbolID)
1434    {
1435        String textChar = null;
1436        int si = SymbolID.getStandardIdentity(symbolID);
1437        int context = SymbolID.getContext(symbolID);
1438        int affiliation = SymbolID.getAffiliation(symbolID);
1439
1440        if(context == SymbolID.StandardIdentity_Context_Simulation)//Simulation
1441            textChar = "S";
1442        else if(context == SymbolID.StandardIdentity_Context_Exercise)
1443        {
1444            if(affiliation == SymbolID.StandardIdentity_Affiliation_Suspect_Joker)//exercise Joker
1445                textChar = "J";
1446            else if(affiliation == SymbolID.StandardIdentity_Affiliation_Hostile_Faker)//exercise faker
1447                textChar = "K";
1448            else if(context == SymbolID.StandardIdentity_Context_Exercise)//exercise
1449                textChar = "X";
1450        }
1451
1452        return textChar;
1453    }
1454
1455    /**
1456     *
1457     * @param symbolID
1458     * @return
1459     */
1460    public static boolean hasRectangleFrame(String symbolID)
1461    {
1462        int affiliation = SymbolID.getAffiliation(symbolID);
1463        int ss = SymbolID.getSymbolSet(symbolID);
1464        if(ss != SymbolID.SymbolSet_ControlMeasure)
1465        {
1466            if (affiliation == SymbolID.StandardIdentity_Affiliation_Friend
1467                    || affiliation == SymbolID.StandardIdentity_Affiliation_AssumedFriend
1468                    || (SymbolID.getContext(symbolID)==SymbolID.StandardIdentity_Context_Exercise &&
1469                    (affiliation == SymbolID.StandardIdentity_Affiliation_Hostile_Faker
1470                            || affiliation == SymbolID.StandardIdentity_Affiliation_Suspect_Joker)))
1471            {
1472                return true;
1473            }
1474            else
1475                return false;
1476        }
1477        else
1478            return false;
1479    }
1480
1481    /**
1482     * Returns the height ratio for the unit specified by the symbol ID
1483     * Based on Figure 4 in 2525E.
1484     * @param symbolID 30 Character {@link String}
1485     * @return {@link Float}
1486     */
1487    public static float getUnitRatioHeight(String symbolID)
1488    {
1489        int ver = SymbolID.getVersion(symbolID);
1490        int aff = SymbolID.getAffiliation(symbolID);
1491
1492        float rh = 0;
1493
1494        if(ver < SymbolID.Version_2525E)
1495        {
1496            int ss = SymbolID.getSymbolSet(symbolID);
1497
1498            if(aff == SymbolID.StandardIdentity_Affiliation_Hostile_Faker ||
1499                    aff == SymbolID.StandardIdentity_Affiliation_Suspect_Joker)
1500            {
1501                switch (ss){
1502                    case SymbolID.SymbolSet_LandCivilianUnit_Organization:
1503                    case SymbolID.SymbolSet_LandUnit:
1504                    case SymbolID.SymbolSet_LandInstallation:
1505                    case SymbolID.SymbolSet_LandEquipment:
1506                    case SymbolID.SymbolSet_SignalsIntelligence_Land:
1507                    case SymbolID.SymbolSet_Activities:
1508                    case SymbolID.SymbolSet_CyberSpace:
1509                        rh = 1.44f;
1510                        break;
1511                    default:
1512                        rh=1.3f;
1513                }
1514            }
1515            else if(aff == SymbolID.StandardIdentity_Affiliation_Friend ||
1516                    aff == SymbolID.StandardIdentity_Affiliation_AssumedFriend)
1517            {
1518                switch (ss){
1519                    case SymbolID.SymbolSet_LandCivilianUnit_Organization:
1520                    case SymbolID.SymbolSet_LandUnit:
1521                    case SymbolID.SymbolSet_LandInstallation:
1522                    case SymbolID.SymbolSet_Activities:
1523                    case SymbolID.SymbolSet_CyberSpace:
1524                        rh = 1f;
1525                        break;
1526                    case SymbolID.SymbolSet_SignalsIntelligence_Land:
1527                    default:
1528                        rh=1.2f;
1529                }
1530            }
1531            else if(aff == SymbolID.StandardIdentity_Affiliation_Neutral)
1532            {
1533                switch (ss){
1534                    case SymbolID.SymbolSet_LandCivilianUnit_Organization:
1535                    case SymbolID.SymbolSet_LandUnit:
1536                    case SymbolID.SymbolSet_LandInstallation:
1537                    case SymbolID.SymbolSet_LandEquipment:
1538                    case SymbolID.SymbolSet_SignalsIntelligence_Land:
1539                    case SymbolID.SymbolSet_Activities:
1540                    case SymbolID.SymbolSet_CyberSpace:
1541                        rh = 1.1f;
1542                        break;
1543                    default:
1544                        rh=1.2f;
1545                }
1546            }
1547            else //UNKNOWN
1548            {
1549                switch (ss){
1550                    case SymbolID.SymbolSet_LandCivilianUnit_Organization:
1551                    case SymbolID.SymbolSet_LandUnit:
1552                    case SymbolID.SymbolSet_LandInstallation:
1553                    case SymbolID.SymbolSet_LandEquipment:
1554                    case SymbolID.SymbolSet_SignalsIntelligence_Land:
1555                    case SymbolID.SymbolSet_Activities:
1556                    case SymbolID.SymbolSet_CyberSpace:
1557                        rh = 1.44f;
1558                        break;
1559                    default:
1560                        rh=1.3f;
1561                }
1562            }
1563        }
1564        else //2525E and up
1565        {
1566            String frameID = SVGLookup.getFrameID(symbolID);
1567            if(frameID.length()==6)
1568                aff = Integer.parseInt(frameID.substring(2,3));
1569            else //"octagon"
1570                return 1f;
1571            char fs = (frameID.charAt(3));
1572
1573            if(aff == SymbolID.StandardIdentity_Affiliation_Hostile_Faker ||
1574                    aff == SymbolID.StandardIdentity_Affiliation_Suspect_Joker)
1575            {
1576                switch (fs){
1577                    case SymbolID.FrameShape_LandUnit:
1578                    case SymbolID.FrameShape_LandInstallation:
1579                    case SymbolID.FrameShape_LandEquipment_SeaSurface:
1580                    case SymbolID.FrameShape_Activity_Event:
1581                    case SymbolID.FrameShape_Cyberspace:
1582                        rh = 1.44f;
1583                        break;
1584                    default:
1585                        rh=1.3f;
1586                }
1587            }
1588            else if(aff == SymbolID.StandardIdentity_Affiliation_Friend ||
1589                    aff == SymbolID.StandardIdentity_Affiliation_AssumedFriend)
1590            {
1591                switch (fs){
1592                    case SymbolID.FrameShape_LandUnit:
1593                    case SymbolID.FrameShape_LandInstallation:
1594                    case SymbolID.FrameShape_Activity_Event:
1595                    case SymbolID.FrameShape_Cyberspace:
1596                        rh = 1f;
1597                        break;
1598                    default:
1599                        rh=1.2f;
1600                }
1601            }
1602            else if(aff == SymbolID.StandardIdentity_Affiliation_Neutral)
1603            {
1604                switch (fs){
1605                    case SymbolID.FrameShape_LandUnit:
1606                    case SymbolID.FrameShape_LandInstallation:
1607                    case SymbolID.FrameShape_LandEquipment_SeaSurface:
1608                    case SymbolID.FrameShape_Activity_Event:
1609                    case SymbolID.FrameShape_Cyberspace:
1610                        rh = 1.1f;
1611                        break;
1612                    default:
1613                        rh=1.2f;
1614                }
1615            }
1616            else //UNKNOWN
1617            {
1618                switch (fs){
1619                    case SymbolID.FrameShape_LandUnit:
1620                    case SymbolID.FrameShape_LandInstallation:
1621                    case SymbolID.FrameShape_LandEquipment_SeaSurface:
1622                    case SymbolID.FrameShape_Activity_Event:
1623                    case SymbolID.FrameShape_Cyberspace:
1624                        rh = 1.44f;
1625                        break;
1626                    default:
1627                        rh=1.3f;
1628                }
1629            }
1630
1631
1632        }
1633
1634        return rh;
1635    }
1636
1637    /**
1638     * Returns the width ratio for the unit specified by the symbol ID
1639     * Based on Figure 4 in 2525E.
1640     * @param symbolID 30 Character {@link String}
1641     * @return {@link Float}
1642     */
1643    public static float getUnitRatioWidth(String symbolID)
1644    {
1645        int ver = SymbolID.getVersion(symbolID);
1646        int aff = SymbolID.getAffiliation(symbolID);
1647
1648        float rw = 0;
1649
1650        if(ver < SymbolID.Version_2525E)
1651        {
1652            int ss = SymbolID.getSymbolSet(symbolID);
1653
1654            if(aff == SymbolID.StandardIdentity_Affiliation_Hostile_Faker ||
1655                    aff == SymbolID.StandardIdentity_Affiliation_Suspect_Joker)
1656            {
1657                switch (ss){
1658                    case SymbolID.SymbolSet_LandCivilianUnit_Organization:
1659                    case SymbolID.SymbolSet_LandUnit:
1660                    case SymbolID.SymbolSet_LandInstallation:
1661                    case SymbolID.SymbolSet_LandEquipment:
1662                    case SymbolID.SymbolSet_SignalsIntelligence_Land:
1663                    case SymbolID.SymbolSet_Activities:
1664                    case SymbolID.SymbolSet_CyberSpace:
1665                        rw = 1.44f;
1666                        break;
1667                    default:
1668                        rw=1.1f;
1669                }
1670            }
1671            else if(aff == SymbolID.StandardIdentity_Affiliation_Friend ||
1672                    aff == SymbolID.StandardIdentity_Affiliation_AssumedFriend)
1673            {
1674                switch (ss){
1675                    case SymbolID.SymbolSet_LandCivilianUnit_Organization:
1676                    case SymbolID.SymbolSet_LandUnit:
1677                    case SymbolID.SymbolSet_LandInstallation:
1678                    case SymbolID.SymbolSet_Activities:
1679                    case SymbolID.SymbolSet_CyberSpace:
1680                        rw = 1.5f;
1681                        break;
1682                    case SymbolID.SymbolSet_LandEquipment:
1683                    case SymbolID.SymbolSet_SignalsIntelligence_Land:
1684                        rw = 1.2f;
1685                        break;
1686                    default:
1687                        rw=1.1f;
1688                }
1689            }
1690            else if(aff == SymbolID.StandardIdentity_Affiliation_Neutral)
1691            {
1692                rw = 1.1f;
1693            }
1694            else //UNKNOWN
1695            {
1696                switch (ss){
1697                    case SymbolID.SymbolSet_LandCivilianUnit_Organization:
1698                    case SymbolID.SymbolSet_LandUnit:
1699                    case SymbolID.SymbolSet_LandInstallation:
1700                    case SymbolID.SymbolSet_LandEquipment:
1701                    case SymbolID.SymbolSet_SignalsIntelligence_Land:
1702                    case SymbolID.SymbolSet_Activities:
1703                    case SymbolID.SymbolSet_CyberSpace:
1704                        rw = 1.44f;
1705                        break;
1706                    default:
1707                        rw=1.5f;
1708                }
1709            }
1710        }
1711        else //2525E and above
1712        {
1713            String frameID = SVGLookup.getFrameID(symbolID);
1714            if(frameID.length()==6)
1715                aff = Integer.parseInt(frameID.substring(2,3));
1716            else //"octagon"
1717                return 1f;
1718            char fs = (frameID.charAt(3));
1719
1720            if(aff == SymbolID.StandardIdentity_Affiliation_Hostile_Faker ||
1721                    aff == SymbolID.StandardIdentity_Affiliation_Suspect_Joker)
1722            {
1723                switch (fs){
1724                    case SymbolID.FrameShape_LandUnit:
1725                    case SymbolID.FrameShape_LandInstallation:
1726                    case SymbolID.FrameShape_LandEquipment_SeaSurface:
1727                    case SymbolID.FrameShape_Activity_Event:
1728                    case SymbolID.FrameShape_Cyberspace:
1729                        rw = 1.44f;
1730                        break;
1731                    default:
1732                        rw=1.1f;
1733                }
1734            }
1735            else if(aff == SymbolID.StandardIdentity_Affiliation_Friend ||
1736                    aff == SymbolID.StandardIdentity_Affiliation_AssumedFriend)
1737            {
1738                switch (fs){
1739                    case SymbolID.FrameShape_LandUnit:
1740                    case SymbolID.FrameShape_LandInstallation:
1741                    case SymbolID.FrameShape_Activity_Event:
1742                    case SymbolID.FrameShape_Cyberspace:
1743                        rw = 1.5f;
1744                        break;
1745                    case SymbolID.FrameShape_LandEquipment_SeaSurface:
1746                        rw = 1.2f;
1747                        break;
1748                    default:
1749                        rw=1.1f;
1750                }
1751            }
1752            else if(aff == SymbolID.StandardIdentity_Affiliation_Neutral)
1753            {
1754                rw = 1.1f;
1755            }
1756            else //UNKNOWN
1757            {
1758                switch (fs){
1759                    case SymbolID.FrameShape_LandUnit:
1760                    case SymbolID.FrameShape_LandInstallation:
1761                    case SymbolID.FrameShape_LandEquipment_SeaSurface:
1762                    case SymbolID.FrameShape_Activity_Event:
1763                    case SymbolID.FrameShape_Cyberspace:
1764                        rw = 1.44f;
1765                        break;
1766                    default:
1767                        rw=1.5f;
1768                }
1769            }
1770        }
1771
1772        return rw;
1773    }
1774
1775    /**
1776     * @param linetype the line type
1777     * @return true if the line is a basic shape
1778     */
1779    public static boolean isBasicShape(int linetype) {
1780        switch (linetype) {
1781            case TacticalLines.BS_AREA:
1782            case TacticalLines.BS_LINE:
1783            case TacticalLines.BS_CROSS:
1784            case TacticalLines.BS_ELLIPSE:
1785            case TacticalLines.PBS_ELLIPSE:
1786            case TacticalLines.PBS_CIRCLE:
1787            case TacticalLines.PBS_SQUARE:
1788            case TacticalLines.PBS_RECTANGLE:
1789            case TacticalLines.BS_RECTANGLE:
1790            case TacticalLines.BBS_AREA:
1791            case TacticalLines.BBS_LINE:
1792            case TacticalLines.BBS_POINT:
1793            case TacticalLines.BBS_RECTANGLE:
1794            case TacticalLines.BS_BBOX:
1795                return true;
1796            default:
1797                return false;
1798        }
1799    }
1800}