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.HostileUnitFillColor;//Color.RED;//0xff0000;   // Red
552                            break;
553                        case SymbolID.StandardIdentity_Affiliation_Suspect_Joker:
554                            if(symStd >= SymbolID.Version_2525E)
555                                retColor = AffiliationColors.SuspectUnitFillColor;//255,188,1
556                            else
557                                retColor = AffiliationColors.HostileUnitFillColor;//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) - 1;//-1 because width might be 37 but the points are only 0 - 36
1154        float centerY = (bounds.height() / 2f) - 1;
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()-1;
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                    else if (ec/100 == 2135) {//2525E sonobuoys
1201                        centerY = (bounds.height() * 0.75f);
1202                    }
1203                    break;
1204                default:
1205            }
1206
1207            switch (ec)
1208            //have to adjust center X as some graphics have integrated text outside the symbol
1209            {
1210                case 180400: //Pickup Point (PUP)
1211                    centerX = bounds.width() * 0.3341f;
1212                    break;
1213                case 240900: //Fire Support Station
1214                    centerX = bounds.width() * 0.38f;
1215                    break;
1216                case 280201: //Antipersonnel Mine with Directional Effects
1217                    centerX = bounds.width() * 0.43f;
1218                    break;
1219                case 182300: //Orbit - Figure Eight
1220                case 182400: //Orbit - Race Track
1221                case 182500: //Orbit - Random Closed
1222                    if(SymbolID.getVersion(symbolID) >= SymbolID.Version_2525E)
1223                        centerY = bounds.height() * 0.27f;
1224                    break;
1225            }
1226        }
1227
1228        return new Point(Math.round(centerX), Math.round(centerY));
1229    }
1230
1231    /**
1232     * Returns true if the symbol is an installation
1233     * @param symbolID 30 Character {@link String}
1234     * @return {@link Boolean}
1235     */
1236    public static Boolean isInstallation(String symbolID)
1237    {
1238        int ss = SymbolID.getSymbolSet(symbolID);
1239        int entity = SymbolID.getEntity(symbolID);
1240        if(ss == SymbolID.SymbolSet_LandInstallation && entity == 11)
1241            return true;
1242        else
1243            return false;
1244    }
1245
1246    /**
1247     * Returns true if the symbol is from an air based symbol set
1248     * @param symbolID 30 Character {@link String}
1249     * @return {@link Boolean}
1250     */
1251    public static Boolean isAir(String symbolID)
1252    {
1253        int ss = SymbolID.getSymbolSet(symbolID);
1254        int entity = SymbolID.getEntity(symbolID);
1255        if(ss == SymbolID.SymbolSet_Air ||
1256                ss == SymbolID.SymbolSet_AirMissile ||
1257                ss == SymbolID.SymbolSet_SignalsIntelligence_Air)
1258            return true;
1259        else
1260            return false;
1261    }
1262
1263    /**
1264     * Returns true if the symbol is from a space based symbol set
1265     * @param symbolID 30 Character {@link String}
1266     * @return {@link Boolean}
1267     */
1268    public static Boolean isSpace(String symbolID)
1269    {
1270        int ss = SymbolID.getSymbolSet(symbolID);
1271        int entity = SymbolID.getEntity(symbolID);
1272        if(ss == SymbolID.SymbolSet_Space ||
1273                ss == SymbolID.SymbolSet_SpaceMissile ||
1274                ss == SymbolID.SymbolSet_SignalsIntelligence_Space)
1275            return true;
1276        else
1277            return false;
1278    }
1279
1280    /**
1281     * Returns true if the symbol is from a land based symbol set
1282     * @param symbolID 30 Character {@link String}
1283     * @return {@link Boolean}
1284     */
1285    public static Boolean isLand(String symbolID)
1286    {
1287        int ss = SymbolID.getSymbolSet(symbolID);
1288        int entity = SymbolID.getEntity(symbolID);
1289        if(ss == SymbolID.SymbolSet_LandUnit ||
1290                ss == SymbolID.SymbolSet_LandCivilianUnit_Organization ||
1291                ss == SymbolID.SymbolSet_LandEquipment ||
1292                ss == SymbolID.SymbolSet_LandInstallation ||
1293                ss == SymbolID.SymbolSet_SignalsIntelligence_Land)
1294            return true;
1295        else
1296            return false;
1297    }
1298
1299    /**
1300     * Returns true if the symbol ID has the task for indicator
1301     * @param symbolID 30 Character {@link String}
1302     * @return {@link Boolean}
1303     */
1304    public static Boolean isTaskForce(String symbolID)
1305    {
1306        int hqtfd = SymbolID.getHQTFD(symbolID);
1307        if((hqtfd == SymbolID.HQTFD_TaskForce ||
1308                hqtfd == SymbolID.HQTFD_TaskForce_Headquarters ||
1309                hqtfd == SymbolID.HQTFD_FeintDummy_TaskForce ||
1310                hqtfd == SymbolID.HQTFD_FeintDummy_TaskForce_Headquarters) &&
1311                SymbolUtilities.canSymbolHaveModifier(symbolID, Modifiers.B_ECHELON))
1312            return true;
1313        else
1314            return false;
1315    }
1316
1317    /**
1318     * Returns true if the symbol ID indicates the context is Reality
1319     * @param symbolID 30 Character {@link String}
1320     * @return {@link Boolean}
1321     */
1322    public static Boolean isReality(String symbolID)
1323    {
1324        int c = SymbolID.getContext(symbolID);
1325        if(c == SymbolID.StandardIdentity_Context_Reality ||
1326                c == 3 || c == 4)
1327            return true;
1328        else
1329            return false;
1330    }
1331
1332    /**
1333     * Returns true if the symbol ID indicates the context is Exercise
1334     * @param symbolID 30 Character {@link String}
1335     * @return {@link Boolean}
1336     */
1337    public static Boolean isExercise(String symbolID)
1338    {
1339        int c = SymbolID.getContext(symbolID);
1340        if(c == SymbolID.StandardIdentity_Context_Exercise ||
1341                c == 5 || c == 6)
1342            return true;
1343        else
1344            return false;
1345    }
1346
1347    /**
1348     * Returns true if the symbol ID indicates the context is Simulation
1349     * @param symbolID 30 Character {@link String}
1350     * @return {@link Boolean}
1351     */
1352    public static Boolean isSimulation(String symbolID)
1353    {
1354        int c = SymbolID.getContext(symbolID);
1355        if(c == SymbolID.StandardIdentity_Context_Simulation ||
1356                c == 7 || c == 8)
1357            return true;
1358        else
1359            return false;
1360    }
1361
1362
1363    /**
1364     * Reads the Symbol ID string and returns the text that represents the echelon
1365     * code.
1366     * @param echelon {@link Integer} from positions 9-10 in the symbol ID
1367     * See {@link SymbolID#getAmplifierDescriptor(String)}
1368     * @return {@link String} (23 (Army) would be "XXXX")
1369     */
1370    public static String getEchelonText(int echelon)
1371    {
1372        char[] dots = new char[3];
1373        dots[0] = (char)8226;
1374        dots[1] = (char)8226;
1375        dots[2] = (char)8226;
1376        String dot = new String(dots);
1377        String text = null;
1378        if(echelon == SymbolID.Echelon_Team_Crew)
1379        {
1380            text = (char) 216 + "";
1381        }
1382        else if(echelon == SymbolID.Echelon_Squad)
1383        {
1384            text = dot.substring(0, 1);
1385        }
1386        else if(echelon == SymbolID.Echelon_Section)
1387        {
1388            text = dot.substring(0, 2);
1389        }
1390        else if(echelon == SymbolID.Echelon_Platoon_Detachment)
1391        {
1392            text = dot;
1393        }
1394        else if(echelon == SymbolID.Echelon_Company_Battery_Troop)
1395        {
1396            text = "I";
1397        }
1398        else if(echelon == SymbolID.Echelon_Battalion_Squadron)
1399        {
1400            text = "II";
1401        }
1402        else if(echelon == SymbolID.Echelon_Regiment_Group)
1403        {
1404            text = "III";
1405        }
1406        else if(echelon == SymbolID.Echelon_Brigade)
1407        {
1408            text = "X";
1409        }
1410        else if(echelon == SymbolID.Echelon_Division)
1411        {
1412            text = "XX";
1413        }
1414        else if(echelon == SymbolID.Echelon_Corps_MEF)
1415        {
1416            text = "XXX";
1417        }
1418        else if(echelon == SymbolID.Echelon_Army)
1419        {
1420            text = "XXXX";
1421        }
1422        else if(echelon == SymbolID.Echelon_ArmyGroup_Front)
1423        {
1424            text = "XXXXX";
1425        }
1426        else if(echelon == SymbolID.Echelon_Region_Theater)
1427        {
1428            text = "XXXXXX";
1429        }
1430        else if(echelon == SymbolID.Echelon_Region_Command)
1431        {
1432            text = "++";
1433        }
1434        return text;
1435    }
1436
1437    /**
1438     * Returns the Standard Identity Modifier based on the Symbol ID
1439     * @param symbolID 30 Character {@link String}
1440     * @return {@link String}
1441     */
1442    public static String getStandardIdentityModifier(String symbolID)
1443    {
1444        String textChar = null;
1445        int si = SymbolID.getStandardIdentity(symbolID);
1446        int context = SymbolID.getContext(symbolID);
1447        int affiliation = SymbolID.getAffiliation(symbolID);
1448
1449        if(context == SymbolID.StandardIdentity_Context_Simulation)//Simulation
1450            textChar = "S";
1451        else if(context == SymbolID.StandardIdentity_Context_Exercise)
1452        {
1453            if(affiliation == SymbolID.StandardIdentity_Affiliation_Suspect_Joker)//exercise Joker
1454                textChar = "J";
1455            else if(affiliation == SymbolID.StandardIdentity_Affiliation_Hostile_Faker)//exercise faker
1456                textChar = "K";
1457            else if(context == SymbolID.StandardIdentity_Context_Exercise)//exercise
1458                textChar = "X";
1459        }
1460
1461        return textChar;
1462    }
1463
1464    /**
1465     *
1466     * @param symbolID
1467     * @return
1468     */
1469    public static boolean hasRectangleFrame(String symbolID)
1470    {
1471        int affiliation = SymbolID.getAffiliation(symbolID);
1472        int ss = SymbolID.getSymbolSet(symbolID);
1473        if(ss != SymbolID.SymbolSet_ControlMeasure)
1474        {
1475            if (affiliation == SymbolID.StandardIdentity_Affiliation_Friend
1476                    || affiliation == SymbolID.StandardIdentity_Affiliation_AssumedFriend
1477                    || (SymbolID.getContext(symbolID)==SymbolID.StandardIdentity_Context_Exercise &&
1478                    (affiliation == SymbolID.StandardIdentity_Affiliation_Hostile_Faker
1479                            || affiliation == SymbolID.StandardIdentity_Affiliation_Suspect_Joker)))
1480            {
1481                return true;
1482            }
1483            else
1484                return false;
1485        }
1486        else
1487            return false;
1488    }
1489
1490    /**
1491     * Returns the height ratio for the unit specified by the symbol ID
1492     * Based on Figure 4 in 2525E.
1493     * @param symbolID 30 Character {@link String}
1494     * @return {@link Float}
1495     */
1496    public static float getUnitRatioHeight(String symbolID)
1497    {
1498        int ver = SymbolID.getVersion(symbolID);
1499        int aff = SymbolID.getAffiliation(symbolID);
1500
1501        float rh = 0;
1502
1503        if(ver < SymbolID.Version_2525E)
1504        {
1505            int ss = SymbolID.getSymbolSet(symbolID);
1506
1507            if(aff == SymbolID.StandardIdentity_Affiliation_Hostile_Faker ||
1508                    aff == SymbolID.StandardIdentity_Affiliation_Suspect_Joker)
1509            {
1510                switch (ss){
1511                    case SymbolID.SymbolSet_LandCivilianUnit_Organization:
1512                    case SymbolID.SymbolSet_LandUnit:
1513                    case SymbolID.SymbolSet_LandInstallation:
1514                    case SymbolID.SymbolSet_LandEquipment:
1515                    case SymbolID.SymbolSet_SignalsIntelligence_Land:
1516                    case SymbolID.SymbolSet_Activities:
1517                    case SymbolID.SymbolSet_CyberSpace:
1518                        rh = 1.44f;
1519                        break;
1520                    default:
1521                        rh=1.3f;
1522                }
1523            }
1524            else if(aff == SymbolID.StandardIdentity_Affiliation_Friend ||
1525                    aff == SymbolID.StandardIdentity_Affiliation_AssumedFriend)
1526            {
1527                switch (ss){
1528                    case SymbolID.SymbolSet_LandCivilianUnit_Organization:
1529                    case SymbolID.SymbolSet_LandUnit:
1530                    case SymbolID.SymbolSet_LandInstallation:
1531                    case SymbolID.SymbolSet_Activities:
1532                    case SymbolID.SymbolSet_CyberSpace:
1533                        rh = 1f;
1534                        break;
1535                    case SymbolID.SymbolSet_SignalsIntelligence_Land:
1536                    default:
1537                        rh=1.2f;
1538                }
1539            }
1540            else if(aff == SymbolID.StandardIdentity_Affiliation_Neutral)
1541            {
1542                switch (ss){
1543                    case SymbolID.SymbolSet_LandCivilianUnit_Organization:
1544                    case SymbolID.SymbolSet_LandUnit:
1545                    case SymbolID.SymbolSet_LandInstallation:
1546                    case SymbolID.SymbolSet_LandEquipment:
1547                    case SymbolID.SymbolSet_SignalsIntelligence_Land:
1548                    case SymbolID.SymbolSet_Activities:
1549                    case SymbolID.SymbolSet_CyberSpace:
1550                        rh = 1.1f;
1551                        break;
1552                    default:
1553                        rh=1.2f;
1554                }
1555            }
1556            else //UNKNOWN
1557            {
1558                switch (ss){
1559                    case SymbolID.SymbolSet_LandCivilianUnit_Organization:
1560                    case SymbolID.SymbolSet_LandUnit:
1561                    case SymbolID.SymbolSet_LandInstallation:
1562                    case SymbolID.SymbolSet_LandEquipment:
1563                    case SymbolID.SymbolSet_SignalsIntelligence_Land:
1564                    case SymbolID.SymbolSet_Activities:
1565                    case SymbolID.SymbolSet_CyberSpace:
1566                        rh = 1.44f;
1567                        break;
1568                    default:
1569                        rh=1.3f;
1570                }
1571            }
1572        }
1573        else //2525E and up
1574        {
1575            String frameID = SVGLookup.getFrameID(symbolID);
1576            if(frameID.length()==6)
1577                aff = Integer.parseInt(frameID.substring(2,3));
1578            else //"octagon"
1579                return 1f;
1580            char fs = (frameID.charAt(3));
1581
1582            if(aff == SymbolID.StandardIdentity_Affiliation_Hostile_Faker ||
1583                    aff == SymbolID.StandardIdentity_Affiliation_Suspect_Joker)
1584            {
1585                switch (fs){
1586                    case SymbolID.FrameShape_LandUnit:
1587                    case SymbolID.FrameShape_LandInstallation:
1588                    case SymbolID.FrameShape_LandEquipment:
1589                    case SymbolID.FrameShape_SeaSurface:
1590                    case SymbolID.FrameShape_Activity_Event:
1591                    case SymbolID.FrameShape_Cyberspace:
1592                        rh = 1.44f;
1593                        break;
1594                    default:
1595                        rh=1.3f;
1596                }
1597            }
1598            else if(aff == SymbolID.StandardIdentity_Affiliation_Friend ||
1599                    aff == SymbolID.StandardIdentity_Affiliation_AssumedFriend)
1600            {
1601                switch (fs){
1602                    case SymbolID.FrameShape_LandUnit:
1603                    case SymbolID.FrameShape_LandInstallation:
1604                    case SymbolID.FrameShape_Activity_Event:
1605                    case SymbolID.FrameShape_Cyberspace:
1606                        rh = 1f;
1607                        break;
1608                    default:
1609                        rh=1.2f;
1610                }
1611            }
1612            else if(aff == SymbolID.StandardIdentity_Affiliation_Neutral)
1613            {
1614                switch (fs){
1615                    case SymbolID.FrameShape_LandUnit:
1616                    case SymbolID.FrameShape_LandInstallation:
1617                    case SymbolID.FrameShape_LandEquipment:
1618                    case SymbolID.FrameShape_SeaSurface:
1619                    case SymbolID.FrameShape_Activity_Event:
1620                    case SymbolID.FrameShape_Cyberspace:
1621                        rh = 1.1f;
1622                        break;
1623                    default:
1624                        rh=1.2f;
1625                }
1626            }
1627            else //UNKNOWN
1628            {
1629                switch (fs){
1630                    case SymbolID.FrameShape_LandUnit:
1631                    case SymbolID.FrameShape_LandInstallation:
1632                    case SymbolID.FrameShape_LandEquipment:
1633                    case SymbolID.FrameShape_SeaSurface:
1634                    case SymbolID.FrameShape_Activity_Event:
1635                    case SymbolID.FrameShape_Cyberspace:
1636                        rh = 1.44f;
1637                        break;
1638                    default:
1639                        rh=1.3f;
1640                }
1641            }
1642
1643
1644        }
1645
1646        return rh;
1647    }
1648
1649    /**
1650     * Returns the width ratio for the unit specified by the symbol ID
1651     * Based on Figure 4 in 2525E.
1652     * @param symbolID 30 Character {@link String}
1653     * @return {@link Float}
1654     */
1655    public static float getUnitRatioWidth(String symbolID)
1656    {
1657        int ver = SymbolID.getVersion(symbolID);
1658        int aff = SymbolID.getAffiliation(symbolID);
1659
1660        float rw = 0;
1661
1662        if(ver < SymbolID.Version_2525E)
1663        {
1664            int ss = SymbolID.getSymbolSet(symbolID);
1665
1666            if(aff == SymbolID.StandardIdentity_Affiliation_Hostile_Faker ||
1667                    aff == SymbolID.StandardIdentity_Affiliation_Suspect_Joker)
1668            {
1669                switch (ss){
1670                    case SymbolID.SymbolSet_LandCivilianUnit_Organization:
1671                    case SymbolID.SymbolSet_LandUnit:
1672                    case SymbolID.SymbolSet_LandInstallation:
1673                    case SymbolID.SymbolSet_LandEquipment:
1674                    case SymbolID.SymbolSet_SignalsIntelligence_Land:
1675                    case SymbolID.SymbolSet_Activities:
1676                    case SymbolID.SymbolSet_CyberSpace:
1677                        rw = 1.44f;
1678                        break;
1679                    default:
1680                        rw=1.1f;
1681                }
1682            }
1683            else if(aff == SymbolID.StandardIdentity_Affiliation_Friend ||
1684                    aff == SymbolID.StandardIdentity_Affiliation_AssumedFriend)
1685            {
1686                switch (ss){
1687                    case SymbolID.SymbolSet_LandCivilianUnit_Organization:
1688                    case SymbolID.SymbolSet_LandUnit:
1689                    case SymbolID.SymbolSet_LandInstallation:
1690                    case SymbolID.SymbolSet_Activities:
1691                    case SymbolID.SymbolSet_CyberSpace:
1692                        rw = 1.5f;
1693                        break;
1694                    case SymbolID.SymbolSet_LandEquipment:
1695                    case SymbolID.SymbolSet_SignalsIntelligence_Land:
1696                        rw = 1.2f;
1697                        break;
1698                    default:
1699                        rw=1.1f;
1700                }
1701            }
1702            else if(aff == SymbolID.StandardIdentity_Affiliation_Neutral)
1703            {
1704                rw = 1.1f;
1705            }
1706            else //UNKNOWN
1707            {
1708                switch (ss){
1709                    case SymbolID.SymbolSet_LandCivilianUnit_Organization:
1710                    case SymbolID.SymbolSet_LandUnit:
1711                    case SymbolID.SymbolSet_LandInstallation:
1712                    case SymbolID.SymbolSet_LandEquipment:
1713                    case SymbolID.SymbolSet_SignalsIntelligence_Land:
1714                    case SymbolID.SymbolSet_Activities:
1715                    case SymbolID.SymbolSet_CyberSpace:
1716                        rw = 1.44f;
1717                        break;
1718                    default:
1719                        rw=1.5f;
1720                }
1721            }
1722        }
1723        else //2525E and above
1724        {
1725            String frameID = SVGLookup.getFrameID(symbolID);
1726            if(frameID.length()==6)
1727                aff = Integer.parseInt(frameID.substring(2,3));
1728            else //"octagon"
1729                return 1f;
1730            char fs = (frameID.charAt(3));
1731
1732            if(aff == SymbolID.StandardIdentity_Affiliation_Hostile_Faker ||
1733                    aff == SymbolID.StandardIdentity_Affiliation_Suspect_Joker)
1734            {
1735                switch (fs){
1736                    case SymbolID.FrameShape_LandUnit:
1737                    case SymbolID.FrameShape_LandInstallation:
1738                    case SymbolID.FrameShape_LandEquipment:
1739                    case SymbolID.FrameShape_SeaSurface:
1740                    case SymbolID.FrameShape_Activity_Event:
1741                    case SymbolID.FrameShape_Cyberspace:
1742                        rw = 1.44f;
1743                        break;
1744                    default:
1745                        rw=1.1f;
1746                }
1747            }
1748            else if(aff == SymbolID.StandardIdentity_Affiliation_Friend ||
1749                    aff == SymbolID.StandardIdentity_Affiliation_AssumedFriend)
1750            {
1751                switch (fs){
1752                    case SymbolID.FrameShape_LandUnit:
1753                    case SymbolID.FrameShape_LandInstallation:
1754                    case SymbolID.FrameShape_Activity_Event:
1755                    case SymbolID.FrameShape_Cyberspace:
1756                        rw = 1.5f;
1757                        break;
1758                    case SymbolID.FrameShape_LandEquipment:
1759                    case SymbolID.FrameShape_SeaSurface:
1760                        rw = 1.2f;
1761                        break;
1762                    default:
1763                        rw=1.1f;
1764                }
1765            }
1766            else if(aff == SymbolID.StandardIdentity_Affiliation_Neutral)
1767            {
1768                rw = 1.1f;
1769            }
1770            else //UNKNOWN
1771            {
1772                switch (fs){
1773                    case SymbolID.FrameShape_LandUnit:
1774                    case SymbolID.FrameShape_LandInstallation:
1775                    case SymbolID.FrameShape_LandEquipment:
1776                    case SymbolID.FrameShape_SeaSurface:
1777                    case SymbolID.FrameShape_Activity_Event:
1778                    case SymbolID.FrameShape_Cyberspace:
1779                        rw = 1.44f;
1780                        break;
1781                    default:
1782                        rw=1.5f;
1783                }
1784            }
1785        }
1786
1787        return rw;
1788    }
1789
1790    /**
1791     * @param linetype the line type
1792     * @return true if the line is a basic shape
1793     */
1794    public static boolean isBasicShape(int linetype) {
1795        switch (linetype) {
1796            case TacticalLines.BS_AREA:
1797            case TacticalLines.BS_LINE:
1798            case TacticalLines.BS_CROSS:
1799            case TacticalLines.BS_ELLIPSE:
1800            case TacticalLines.PBS_ELLIPSE:
1801            case TacticalLines.PBS_CIRCLE:
1802            case TacticalLines.PBS_SQUARE:
1803            case TacticalLines.PBS_RECTANGLE:
1804            case TacticalLines.BS_RECTANGLE:
1805            case TacticalLines.BBS_AREA:
1806            case TacticalLines.BBS_LINE:
1807            case TacticalLines.BBS_POINT:
1808            case TacticalLines.BBS_RECTANGLE:
1809            case TacticalLines.BS_BBOX:
1810            case TacticalLines.BS_ROUTE:
1811            case TacticalLines.BS_TRACK:
1812            case TacticalLines.BS_RADARC:
1813            case TacticalLines.BS_CAKE:
1814            case TacticalLines.BS_ORBIT:
1815            case TacticalLines.BS_POLYARC:
1816                return true;
1817            default:
1818                return false;
1819        }
1820    }
1821}