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