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     */
345    public static String getBasicSymbolID2525C(String strSymbolID)
346    {
347        if(strSymbolID != null && strSymbolID.length() == 15)
348        {
349            StringBuilder sb = new StringBuilder();
350            char scheme = strSymbolID.charAt(0);
351            if (scheme == 'G')
352            {
353                sb.append(strSymbolID.charAt(0));
354                sb.append("*");
355                sb.append(strSymbolID.charAt(2));
356                sb.append("*");
357                sb.append(strSymbolID.substring(4, 10));
358                sb.append("****X");
359            }
360            else if (scheme != 'W' && scheme != 'B' && scheme != 'P')
361            {
362                sb.append(strSymbolID.charAt(0));
363                sb.append("*");
364                sb.append(strSymbolID.charAt(2));
365                sb.append("*");
366                sb.append(strSymbolID.substring(4, 10));
367                sb.append("*****");
368            }
369            else
370            {
371                return strSymbolID;
372            }
373            return sb.toString();
374        }
375        return strSymbolID;
376    }
377
378    /**
379     * Attempts to resolve a bad symbol ID into a value that can be found in {@link MSLookup}.
380     * If it fails, it will return the symbol code for a invalid symbol which is displayed as
381     * an inverted question mark (110098000010000000000000000000)
382     * @param symbolID 30 character {@link String}
383     * @return 30 character {@link String} representing the resolved symbol ID.
384     */
385    public static String reconcileSymbolID(String symbolID)
386    {
387
388        String newID = "";
389        try {
390
391
392            int v = SymbolID.getVersion(symbolID);
393            if (v < SymbolID.Version_APP6D)
394                newID = String.valueOf(SymbolID.Version_2525Dch1);
395            else if(v > SymbolID.Version_APP6Ech2)
396                newID = String.valueOf(SymbolID.Version_2525Ech1);
397            v = SymbolID.getVersion(newID);
398            int c = SymbolID.getContext(symbolID);
399            if (c > 4)
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 blank symbol
455                newID += "000000";
456                /*//set to invalid symbol since we couldn't find it in the lookup
457                newID = SymbolID.setSymbolSet(newID, 98);
458                newID += 100000;//*/
459            }
460            else
461                newID += String.format("%06d",ec);//we found it so add the entity code
462
463            //newID += SymbolID.getMod1ID(symbolID);//just add, won't get used if value bad
464            //newID += SymbolID.getMod2ID(symbolID);//just add, won't get used if value bad
465            newID += symbolID.substring(16);//just add, won't get used if value bad
466        }
467        catch(Exception exc)
468        {
469            newID = "110098000010000000000000000000";//invalid symbol
470        }
471
472        return newID;
473    }
474
475    /**
476     * Gets line color used if no line color has been set. The color is specified based on the affiliation of
477     * the symbol and whether it is a unit or not.
478     * @param symbolID 30 character {@link String}
479     * @return {@link Color}
480     */
481    public static Color getLineColorOfAffiliation(String symbolID)
482    {
483        Color retColor = null;
484
485        int symbolSet = SymbolID.getSymbolSet(symbolID);
486        int set = SymbolID.getSymbolSet(symbolID);
487        int affiliation = SymbolID.getAffiliation(symbolID);
488        int symStd = SymbolID.getVersion(symbolID);
489        int entityCode = SymbolID.getEntityCode(symbolID);
490
491        try
492        {
493            // We can't get the line color if there is no symbol id, since that also means there is no affiliation
494            if((symbolID == null) || (symbolID.equals("")))
495            {
496                return retColor;
497            }
498
499            if(symbolSet == SymbolID.SymbolSet_ControlMeasure)
500            {
501                int entity = SymbolID.getEntity(symbolID);
502                int entityType = SymbolID.getEntityType(symbolID);
503                int entitySubtype = SymbolID.getEntitySubtype(symbolID);
504
505                if(SymbolUtilities.isGreenProtectionGraphic(entity, entityType, entitySubtype))
506                {
507                    //Obstacles/Protection Graphics, some are green obstacles and we need to
508                    //check for those.
509                    retColor = AffiliationColors.ObstacleGreen;
510                }
511                //just do color by affiliation if no other color has been set yet.
512                if(retColor == null)
513                {
514                    switch (affiliation) {
515                        case SymbolID.StandardIdentity_Affiliation_Friend:
516                        case SymbolID.StandardIdentity_Affiliation_AssumedFriend:
517                            retColor = AffiliationColors.FriendlyGraphicLineColor;//Color.BLACK;//0x000000;     // Black
518                            break;
519                        case SymbolID.StandardIdentity_Affiliation_Hostile_Faker:
520                            retColor = AffiliationColors.HostileGraphicLineColor;//Color.RED;//0xff0000;        // Red
521                            break;
522                        case SymbolID.StandardIdentity_Affiliation_Suspect_Joker:
523                            if(symStd >= SymbolID.Version_2525E)
524                                retColor = AffiliationColors.SuspectGraphicLineColor;//255,188,1
525                            else
526                                retColor = AffiliationColors.HostileGraphicLineColor;//Color.RED;//0xff0000;    // Red
527                            break;
528                        case SymbolID.StandardIdentity_Affiliation_Neutral:
529                            retColor = AffiliationColors.NeutralGraphicLineColor;//Color.GREEN;//0x00ff00;      // Green
530                            break;
531                        default:
532                            retColor = AffiliationColors.UnknownGraphicLineColor;//Color.YELLOW;//0xffff00;     // Yellow
533                            break;
534                    }
535                }
536            }
537            else if (set >= 45 && set <= 47)//METOC
538            {
539                // If not black then color will be set in clsMETOC.SetMeTOCProperties()
540                retColor = Color.BLACK;
541            }
542            else if (set == SymbolID.SymbolSet_MineWarfare && (RendererSettings.getInstance().getSeaMineRenderMethod() == RendererSettings.SeaMineRenderMethod_MEDAL))
543            {
544                if(!(entityCode == 110600 || entityCode == 110700))
545                {
546                    switch(affiliation)
547                    {
548                        case SymbolID.StandardIdentity_Affiliation_Friend:
549                        case SymbolID.StandardIdentity_Affiliation_AssumedFriend:
550                            retColor = AffiliationColors.FriendlyUnitFillColor;//0x00ffff;      // Cyan
551                            break;
552                        case SymbolID.StandardIdentity_Affiliation_Hostile_Faker:
553                            retColor = AffiliationColors.HostileUnitFillColor;//Color.RED;//0xff0000;   // Red
554                            break;
555                        case SymbolID.StandardIdentity_Affiliation_Suspect_Joker:
556                            if(symStd >= SymbolID.Version_2525E)
557                                retColor = AffiliationColors.SuspectUnitFillColor;//255,188,1
558                            else
559                                retColor = AffiliationColors.HostileUnitFillColor;//Color.RED;//0xff0000;       // Red
560                            break;
561                        case SymbolID.StandardIdentity_Affiliation_Neutral:
562                            retColor = AffiliationColors.NeutralUnitFillColor;//0x7fff00;       // Light Green
563                            break;
564                        default://unknown, pending, everything else
565                            retColor = AffiliationColors.UnknownUnitFillColor;//new Color(255,250, 205); //0xfffacd;    // LemonChiffon 255 250 205
566                            break;
567                    }
568                }
569                else
570                {
571                    retColor = Color.BLACK;
572                }
573            }
574            else//everything else
575            {
576                //stopped doing check because all warfighting
577                //should have black for line color.
578                retColor = Color.BLACK;
579            }
580        }
581        catch(Exception e)
582        {
583            // Log Error
584            ErrorLogger.LogException("SymbolUtilities", "getLineColorOfAffiliation", e);
585            //throw e;
586        }       // End catch
587        return retColor;
588    }   // End get LineColorOfAffiliation
589
590    /**
591     * For Control Measures, returns the default color for a symbol when it differs from the
592     * affiliation line color.  If there is no default color, returns the value from {@link #getLineColorOfAffiliation}
593     * @param symbolID 30 Character {@link String}
594     * @return {@link Color}
595     */
596    public static Color getDefaultLineColor(String symbolID) {
597        try {
598            if (symbolID == null || symbolID.equals("")) {
599                return null;
600            }
601
602            int symbolSet = SymbolID.getSymbolSet(symbolID);
603            int entityCode = SymbolID.getEntityCode(symbolID);
604            int version = SymbolID.getVersion(symbolID);
605
606            if (symbolSet == SymbolID.SymbolSet_ControlMeasure) {
607                if (entityCode == 200600) {
608                    return Color.WHITE;
609                } else if (entityCode == 200700) {
610                    return new Color(51, 136, 136);
611                } else if (entityCode == 200101) {
612                    return new Color(255, 155, 0);
613                } else if (entityCode == 200201 || entityCode == 200202) {
614                    return new Color(85, 119, 136);
615                } else if (version >= SymbolID.Version_2525E &&
616                        (entityCode == 132100 || //key terrain
617                                entityCode == 282001 || //Tower, Low
618                                entityCode == 282002 || //Tower, High
619                                entityCode == 282003)) { // Overhead Wire
620                    return new Color(128, 0, 128);//purple
621                }
622            }
623        } catch (Exception e) {
624            ErrorLogger.LogException("SymbolUtilities", "getDefaultLineColor", e);
625        }
626        return getLineColorOfAffiliation(symbolID);
627    }
628
629    /**
630     * Checks if a symbol should be filled by default
631     * 
632     * @param strSymbolID The 20 digit representation of the 2525D symbol
633     * @return true if there is a default fill
634     */
635    public static boolean hasDefaultFill(String strSymbolID) {
636        int ec = SymbolID.getEntityCode(strSymbolID);
637        switch (ec) {
638            case 200101:
639            case 200201:
640            case 200202:
641            case 200600:
642            case 200700:
643               return true;
644            default:
645                return !SymbolUtilities.isTacticalGraphic(strSymbolID);
646        }
647    }
648
649    /**
650     * Determines if the symbol is a tactical graphic
651     *
652     * @param strSymbolID 30 Character {@link String}
653     * @return true if symbol set is 25 (control measure), or is a weather graphic
654     */
655    public static boolean isTacticalGraphic(String strSymbolID) {
656        try {
657            int ss = SymbolID.getSymbolSet(strSymbolID);
658
659            if(ss == SymbolID.SymbolSet_ControlMeasure || isWeather(strSymbolID)) {
660                return true;
661            }
662        }
663        catch (Exception e) {
664            ErrorLogger.LogException("SymbolUtilities", "getFillColorOfAffiliation", e);
665        }
666        return false;
667    }
668
669    /**
670     * Determines if the Symbol can be rendered as a multipoint graphic and not just as an icon
671     * @param symbolID 30 Character {@link String}
672     * @return {@link Boolean}
673     */
674    public static boolean isMultiPoint(String symbolID)
675    {
676        MSInfo msi = MSLookup.getInstance().getMSLInfo(symbolID);
677        if (msi == null) {
678            return false;
679        }
680        int drawRule = msi.getDrawRule();
681        int ss = msi.getSymbolSet();
682        if(ss != SymbolID.SymbolSet_ControlMeasure && ss != SymbolID.SymbolSet_Oceanographic && ss != SymbolID.SymbolSet_Atmospheric && ss != SymbolID.SymbolSet_MeteorologicalSpace)
683        {
684            return false;
685        }
686        else if (ss == SymbolID.SymbolSet_ControlMeasure)
687        {
688            if(msi.getMaxPointCount() > 1)
689                return true;
690            else if((drawRule < DrawRules.POINT1 || drawRule > DrawRules.POINT16 || drawRule == DrawRules.POINT12) &&
691                    drawRule != DrawRules.DONOTDRAW && drawRule != DrawRules.AREA22)
692            {
693                return true;
694            }
695            else
696                return false;
697        }
698        else if(ss == SymbolID.SymbolSet_Oceanographic || ss == SymbolID.SymbolSet_Atmospheric || ss == SymbolID.SymbolSet_MeteorologicalSpace)
699        {
700            if(msi.getMaxPointCount() > 1)
701                return true;
702            else
703                return false;
704        }
705        return false;
706    }
707
708    public static boolean isActionPoint(String symbolID)
709    {
710        MSInfo msi = MSLookup.getInstance().getMSLInfo(symbolID);
711        if(msi.getDrawRule()==DrawRules.POINT1)
712        {
713            int ec = SymbolID.getEntityCode(symbolID);
714            if(ec != 131300 && ec != 131301 && ec != 182600 && ec != 212800
715                    && ec != 360100 && ec != 360200 && ec != 360300)
716                return true;
717        }
718        return false;
719    }
720
721    /**
722     * Control Measures and Tactical Graphics that have labels but not with the Action Point layout
723     * @param strSymbolID 30 Character {@link String}
724     * @return {@link Boolean}
725     + @deprecated see {@link #isSPWithSpecialModifierLayout(String)}
726     */
727    public static boolean isTGSPWithSpecialModifierLayout(String strSymbolID)
728    {
729        try
730        {
731            int ss = SymbolID.getSymbolSet(strSymbolID);
732            int entityCode = SymbolID.getEntityCode(strSymbolID);
733            if(ss == SymbolID.SymbolSet_ControlMeasure) //|| isWeather(strSymbolID)) {
734            {
735                if(SymbolUtilities.isCBRNEvent(strSymbolID))
736                    return true;
737
738                if(SymbolUtilities.isSonobuoy(strSymbolID))
739                    return true;
740
741                switch (entityCode)
742                {
743                    case 130500: //contact point
744                    case 130700: //decision point
745                    case 212800: //harbor
746                    case 210300: //Defended Asset
747                    case 210600: //Air Detonation
748                    case 131300: //point of interest
749                    case 131800: //waypoint
750                    case 240900: //fire support station
751                    case 180100: //Air Control point
752                    case 180200: //Communications Check point
753                    case 160300: //T (target reference point)
754                    case 240601: //ap,ap1,x,h (Point/Single Target)
755                    case 240602: //ap (nuclear target)
756                    case 270701: //static depiction
757                    case 282001: //tower, low
758                    case 282002: //tower, high
759                        return true;
760                    default:
761                        return false;
762                }
763            }
764            else if(ss == SymbolID.SymbolSet_Atmospheric)
765            {
766                switch (entityCode)
767                {
768                    case 162300: //Freezing Level
769                    case 162200: //tropopause Level
770                    case 110102: //tropopause Low
771                    case 110202: //tropopause High
772                        return true;
773                    default:
774                        return false;
775                }
776            }
777        }
778        catch (Exception e) {
779            ErrorLogger.LogException("SymbolUtilities", "getFillColorOfAffiliation", e);
780        }
781        return false;
782    }
783
784    /**
785     * Returns the fill color for the symbol based on its affiliation
786     * @param symbolID 30 Character {@link String}
787     * @return {@link Color}
788     */
789    public static Color getFillColorOfAffiliation(String symbolID)
790    {
791        Color retColor = null;
792        int entityCode = SymbolID.getEntityCode(symbolID);
793        int entity = SymbolID.getEntity(symbolID);
794        int entityType = SymbolID.getEntityType(symbolID);
795        int entitySubtype = SymbolID.getEntitySubtype(symbolID);
796
797        int affiliation = SymbolID.getAffiliation(symbolID);
798
799        try
800        {
801            // We can't get the fill color if there is no symbol id, since that also means there is no affiliation
802            if ((symbolID == null) || (symbolID.equals(""))) {
803                return retColor;
804            }
805            if (SymbolID.getSymbolSet(symbolID) == SymbolID.SymbolSet_ControlMeasure) {
806                switch (entityCode) {
807                    case 200101:
808                        retColor = new Color(255, 155, 0, (int) (.25 * 255));
809                        break;
810                    case 200201:
811                    case 200202:
812                    case 200600:
813                        retColor = new Color(85, 119, 136, (int) (.25 * 255));
814                        break;
815                    case 200700:
816                        retColor = new Color(51, 136, 136, (int) (.25 * 255));
817                        break;
818                }
819            }
820            else if (SymbolID.getSymbolSet(symbolID) == SymbolID.SymbolSet_MineWarfare &&
821                    (RendererSettings.getInstance().getSeaMineRenderMethod() == RendererSettings.SeaMineRenderMethod_MEDAL) &&
822                    (!(entityCode == 110600 || entityCode == 110700)))
823            {
824                retColor = new Color(0,0,0,0);//transparent
825            }
826            //just do color by affiliation if no other color has been set yet
827            if (retColor == null) {
828                switch(affiliation)
829                {
830                    case SymbolID.StandardIdentity_Affiliation_Friend:
831                    case SymbolID.StandardIdentity_Affiliation_AssumedFriend:
832                        retColor = AffiliationColors.FriendlyUnitFillColor;//0x00ffff;  // Cyan
833                        break;
834                    case SymbolID.StandardIdentity_Affiliation_Hostile_Faker:
835                        retColor = AffiliationColors.HostileUnitFillColor;//0xfa8072;   // Salmon
836                        break;
837                    case SymbolID.StandardIdentity_Affiliation_Suspect_Joker:
838                        if(SymbolID.getVersion(symbolID) >= SymbolID.Version_2525E)
839                            retColor = AffiliationColors.SuspectGraphicFillColor;//255,229,153
840                        else
841                            retColor = AffiliationColors.HostileGraphicFillColor;//Color.RED;//0xff0000;        // Red
842                        break;
843                    case SymbolID.StandardIdentity_Affiliation_Neutral:
844                        retColor = AffiliationColors.NeutralUnitFillColor;//0x7fff00;   // Light Green
845                        break;
846                    default://unknown, pending, everything else
847                        retColor = AffiliationColors.UnknownUnitFillColor;//new Color(255,250, 205); //0xfffacd;        // LemonChiffon 255 250 205
848                        break;
849                }
850            }
851        } // End try
852        catch (Exception e)
853        {
854            // Log Error
855            ErrorLogger.LogException("SymbolUtilities", "getFillColorOfAffiliation", e);
856            //throw e;
857        }       // End catch
858
859        return retColor;
860    }   // End FillColorOfAffiliation
861
862    /**
863     *
864     * @param symbolID 30 Character {@link String}
865     * @param modifier {@link Modifiers} 
866     * @return {@link Boolean}
867     * @deprecated see {@link #hasModifier(String, String)}
868     */
869    public static Boolean canSymbolHaveModifier(String symbolID, String modifier)
870    {
871        return hasModifier(symbolID, modifier);
872    }
873
874    /**
875     * Checks if the Symbol Code has FDI set.
876     * Does not check if the symbol can have an FDI.
877     * @param symbolID 30 Character {@link String}
878     * @return {@link Boolean}
879     */
880    public static Boolean hasFDI(String symbolID)
881    {
882        int hqtfd = SymbolID.getHQTFD(symbolID);
883        if(hqtfd == SymbolID.HQTFD_FeintDummy
884                || hqtfd == SymbolID.HQTFD_FeintDummy_TaskForce
885                || hqtfd == SymbolID.HQTFD_FeintDummy_Headquarters
886                || hqtfd == SymbolID.HQTFD_FeintDummy_TaskForce_Headquarters)
887        {
888            return true;
889        }
890        else
891            return false;
892    }
893
894    /*
895     * For Renderer Use Only
896     * Assumes a fresh SVG String from the SVGLookup with its default values
897     * @param symbolID
898     * @param svg
899     * @param strokeColor
900     * @param fillColor
901     * @return
902     */
903    /*public static String setSVGFrameColors(String symbolID, String svg, Color strokeColor, Color fillColor)
904    {
905        String hexStrokeColor = null;
906        String hexFillColor = null;
907
908        if(strokeColor != null)
909            hexStrokeColor = colorToHexString(strokeColor,false);
910        if(fillColor != null)
911            hexFillColor = colorToHexString(fillColor,false);
912        return setSVGFrameColors(symbolID, svg, hexStrokeColor,hexFillColor);
913    }//*/
914
915    /***
916     * Returns true if graphic is protection graphic (obstacles which render green)
917     * Assumes control measure symbol code where SS == 25
918     * @param entity {@link Integer}
919     * @param entityType {@link Integer}
920     * @param entitySubtype {@link Integer}
921     * @return {@link Boolean}
922     */
923    public static boolean isGreenProtectionGraphic(int entity, int entityType, int entitySubtype)
924    {
925        if(entity >= 27 && entity <= 29)//Protection Areas, Points and Lines
926        {
927            if(entity == 27)
928            {
929                if(entityType > 0 && entityType <= 5)
930                    return true;
931                else if(entityType == 7 || entityType == 8 || entityType == 10 || entityType == 12)
932                {
933                    return true;
934                }
935                else
936                    return false;
937            }
938            else if(entity == 28)
939            {
940                if(entityType > 0 && entityType <= 7)
941                    return true;
942                if(entityType == 19)
943                    return true;
944                else
945                    return false;
946            }
947            else if(entity == 29)
948            {
949                if(entityType >= 01 && entityType <= 05)
950                    return true;
951                else
952                    return false;
953            }
954        }
955        else
956        {
957            return false;
958        }
959        return false;
960    }
961
962    /**
963     * Returns true if graphic is protection graphic (obstacles which render green)
964     * @param symbolID 30 Character {@link String}
965     * @return {@link Boolean}
966     */
967    public static boolean isGreenProtectionGraphic(String symbolID){
968        if (SymbolID.getSymbolSet(symbolID) == SymbolID.SymbolSet_ControlMeasure) {
969            return SymbolUtilities.isGreenProtectionGraphic(SymbolID.getEntity(symbolID), SymbolID.getEntityType(symbolID), SymbolID.getEntitySubtype(symbolID));
970        } else {
971            return false;
972        }
973    }
974
975    /**
976     * Returns true if Symbol ID represents a chemical, biological, radiological or nuclear incident.
977     * @param symbolID 30 Character {@link String}
978     * @return {@link Boolean}
979     */
980    public static boolean isCBRNEvent(String symbolID)
981    {
982        int ss = SymbolID.getSymbolSet(symbolID);
983        int ec = SymbolID.getEntityCode(symbolID);
984
985        if(ss == SymbolID.SymbolSet_ControlMeasure) {
986            switch (ec)
987            {
988                case 281300:
989                case 281301:
990                case 281400:
991                case 281401:
992                case 281500:
993                case 281600:
994                case 281700:
995                case 281701:
996                    return true;
997                default:
998            }
999        }
1000        return false;
1001    }
1002
1003    /**
1004     * Returns true if Symbol ID represents a Sonobuoy.
1005     * @param symbolID 30 Character {@link String}
1006     * @return {@link Boolean}
1007     */
1008    public static boolean isSonobuoy(String symbolID)
1009    {
1010        int ss = SymbolID.getSymbolSet(symbolID);
1011        int e = SymbolID.getEntity(symbolID);
1012        int et = SymbolID.getEntityType(symbolID);
1013        if(ss == 25 && e == 21 && et == 35)
1014            return true;
1015        else
1016            return false;
1017    }
1018
1019    /**
1020     * Obstacles are generally required to have a green line color
1021     * @param symbolID 30 Character {@link String}
1022     * @return {@link Boolean}
1023     * @deprecated see {@link #isGreenProtectionGraphic(String)}
1024     */
1025    public static boolean isObstacle(String symbolID)
1026    {
1027
1028        if(SymbolID.getSymbolSet(symbolID) == SymbolID.SymbolSet_ControlMeasure &&
1029                SymbolID.getEntity(symbolID) == 27)
1030        {
1031            return true;
1032        }
1033        else
1034            return false;
1035    }
1036
1037    /**
1038     * Return true if symbol is from the Atmospheric, Oceanographic or Meteorological Space Symbol Sets.
1039     * @param symbolID 30 Character {@link String}
1040     * @return {@link Boolean}
1041     */
1042    public static boolean isWeather(String symbolID)
1043    {
1044        int ss = SymbolID.getSymbolSet(symbolID);
1045        if(ss >= SymbolID.SymbolSet_Atmospheric && ss <= SymbolID.SymbolSet_MeteorologicalSpace)
1046            return true;
1047        else
1048            return false;
1049    }
1050
1051    /**
1052     * Returns true if the symbol has the HQ staff indicated by the symbol ID
1053     * @param symbolID 30 Character {@link String}
1054     * @return {@link Boolean}
1055     */
1056    public static boolean isHQ(String symbolID)
1057    {
1058        int hq = SymbolID.getHQTFD(symbolID);
1059        if(SymbolUtilities.hasModifier(symbolID, Modifiers.S_HQ_STAFF_INDICATOR) &&
1060                (hq == SymbolID.HQTFD_FeintDummy_Headquarters ||
1061                        hq == SymbolID.HQTFD_Headquarters  ||
1062                        hq == SymbolID.HQTFD_FeintDummy_TaskForce_Headquarters ||
1063                        hq == SymbolID.HQTFD_TaskForce_Headquarters))
1064            return true;
1065        else
1066            return false;
1067    }
1068
1069    /**
1070     * Checks if this is a single point control measure  or meteorological graphic with a unique layout.
1071     * Basically anything that's not an action point style graphic with modifiers
1072     * @param symbolID 30 Character {@link String}
1073     * @return {@link Boolean}
1074     */
1075    public static boolean isSPWithSpecialModifierLayout(String symbolID)
1076    {
1077        int ss = SymbolID.getSymbolSet(symbolID);
1078        int ec = SymbolID.getEntityCode(symbolID);
1079
1080        if(ss == SymbolID.SymbolSet_ControlMeasure)
1081        {
1082            switch(ec)
1083            {
1084                case 130500: //Control Point
1085                case 130700: //Decision Point
1086                case 131300: //Point of Interest
1087                case 131800: //Waypoint
1088                case 131900: //Airfield (AEGIS Only)
1089                case 132000: //Target Handover
1090                case 132100: //Key Terrain
1091                case 132300: //Vital Ground
1092                case 160300: //Target Point Reference
1093                case 180100: //Air Control Point
1094                case 180200: //Communications Check Point
1095                case 180600: //TACAN
1096                case 210300: //Defended Asset
1097                case 210600: //Air Detonation
1098                case 210800: //Impact Point
1099                case 211000: //Launched Torpedo
1100                case 212800: //Harbor
1101                case 213400: //Navigational reference waypoint
1102                case 213500: //Sonobuoy
1103                case 213501: //Ambient Noise Sonobuoy
1104                case 213502: //Air Transportable Communication (ATAC) (Sonobuoy)
1105                case 213503: //Barra (Sonobuoy)
1106                case 213504:
1107                case 213505:
1108                case 213506:
1109                case 213507:
1110                case 213508:
1111                case 213509:
1112                case 213510:
1113                case 213511:
1114                case 213512:
1115                case 213513:
1116                case 213514:
1117                case 213515:
1118                case 214900: //General Sea Subsurface Station
1119                case 215600: //General Sea Station
1120                case 217000: //Shore Control Station
1121                case 240601: //Point or Single Target
1122                case 240602: //Nuclear Target
1123                case 240900: //Fire Support Station
1124                case 250600: //Known Point
1125                case 270701: //Static Depiction
1126                case 282001: //Tower, Low
1127                case 282002: //Tower, High
1128                case 281300: //Chemical Event
1129                case 281301: //Chemical Event - toxic material
1130                case 281400: //Biological Event
1131                case 281402: //Biological Event - toxic material
1132                case 281500: //Nuclear Event
1133                case 281600: //Nuclear Fallout Producing Event
1134                case 281700: //Radiological Event
1135                case 281701: //Radiological Event - toxic material
1136                case 360100: //Protection of cultural property - General
1137                case 360200: //Protection of cultural property - Special
1138                case 360300: //Protection of cultural property - Enhanced
1139                    return true;
1140                default:
1141                    return false;
1142            }
1143        }
1144        else if(ss == SymbolID.SymbolSet_Atmospheric)
1145        {
1146            switch(ec)
1147            {
1148                case 162300: //Freezing Level
1149                case 162200: //tropopause Level
1150                case 110102: //tropopause low
1151                case 110202: //tropopause high
1152                    return true;
1153                default:
1154                    return false;
1155            }
1156        }
1157        return false;
1158    }
1159
1160    /**
1161     * Gets the anchor point for single point Control Measure as the anchor point isn't always they center of the symbol.
1162     * @param symbolID 30 Character {@link String}
1163     * @param bounds {@link RectF} representing the bound of the core symbol in the image.
1164     * @return {@link Point} representing the point in the image that is the anchor point of the symbol.
1165     */
1166    public static Point getCMSymbolAnchorPoint(String symbolID, RectF bounds) {
1167        float centerX = (bounds.width() / 2f) - 1;//-1 because width might be 37 but the points are only 0 - 36
1168        float centerY = (bounds.height() / 2f) - 1;
1169
1170        int ss = SymbolID.getSymbolSet(symbolID);
1171        int ec = SymbolID.getEntityCode(symbolID);
1172        int drawRule = 0;
1173
1174        //center/anchor point is always half width and half height except for control measures
1175        //and meteorological
1176        if (ss == SymbolID.SymbolSet_ControlMeasure) {
1177            drawRule = MSLookup.getInstance().getMSLInfo(symbolID).getDrawRule();
1178            switch (drawRule)//here we check the 'Y' value for the anchor point
1179            {
1180                case DrawRules.POINT1://action points 1301 //bottom center
1181                case DrawRules.POINT5://entry point 2105
1182                case DrawRules.POINT6://ground zero 2107
1183                case DrawRules.POINT7://missile detection point 2111
1184                    centerY = bounds.height()-1;
1185                    break;
1186                case DrawRules.POINT4://drop point 2104 //almost bottom and center
1187                    centerY = (bounds.height() * 0.80f);
1188                    break;
1189                case DrawRules.POINT10://Sonobuoy 2135 //center of circle which isn't center of symbol
1190                    centerY = (bounds.height() * 0.75f);
1191                    break;
1192                case DrawRules.POINT13://booby trap 2807 //almost bottom and center
1193                    centerY = (bounds.height() * 0.74f);
1194                    break;
1195                case DrawRules.POINT15://Marine Life 2189 //center left
1196                    centerX = 0;
1197                    break;
1198                case DrawRules.POINT16://Tower 282001 //circle at base of tower
1199                    centerY = (bounds.height() * 0.87f);
1200                    break;
1201                case DrawRules.POINT2://Several different symbols
1202                    if (ec == 280500)//Wide Area Antitank Mine
1203                        centerY = (bounds.height() * 0.35f);
1204                    else if (ec == 280400)//Antitank Mine w/ Anti-handling Device
1205                        centerY = (bounds.height() * 0.33f);
1206                    else if (ec == 280200)//Antipersonnel Mine
1207                        centerY = (bounds.height() * 0.7f);
1208                    else if (ec == 280201)//Antipersonnel Mine with Directional Effects
1209                        centerY = (bounds.height() * 0.65f);
1210                    else if (ec == 219000)//Sea Anomaly
1211                        centerY = (bounds.height() * 0.7f);
1212                    else if (ec == 212500)//Electromagnetic - Magnetic Anomaly Detections (MAD)
1213                        centerY = (bounds.height() * 0.4f);
1214                    else if (ec/100 == 2135) {//2525E sonobuoys
1215                        centerY = (bounds.height() * 0.75f);
1216                    }
1217                    break;
1218                default:
1219            }
1220
1221            switch (ec)
1222            //have to adjust center X as some graphics have integrated text outside the symbol
1223            {
1224                case 180400: //Pickup Point (PUP)
1225                    centerX = bounds.width() * 0.3341f;
1226                    break;
1227                case 240900: //Fire Support Station
1228                    centerX = bounds.width() * 0.38f;
1229                    break;
1230                case 280201: //Antipersonnel Mine with Directional Effects
1231                    centerX = bounds.width() * 0.43f;
1232                    break;
1233                case 182300: //Orbit - Figure Eight
1234                case 182400: //Orbit - Race Track
1235                case 182500: //Orbit - Random Closed
1236                    if(SymbolID.getVersion(symbolID) >= SymbolID.Version_2525E)
1237                        centerY = bounds.height() * 0.27f;
1238                    break;
1239            }
1240        }
1241
1242        return new Point(Math.round(centerX), Math.round(centerY));
1243    }
1244
1245    /**
1246     * Returns true if the symbol is an installation
1247     * @param symbolID 30 Character {@link String}
1248     * @return {@link Boolean}
1249     */
1250    public static Boolean isInstallation(String symbolID)
1251    {
1252        int ss = SymbolID.getSymbolSet(symbolID);
1253        int entity = SymbolID.getEntity(symbolID);
1254        if(ss == SymbolID.SymbolSet_LandInstallation && entity == 11)
1255            return true;
1256        else
1257            return false;
1258    }
1259
1260    /**
1261     * Returns true if the symbol is from an air based symbol set
1262     * @param symbolID 30 Character {@link String}
1263     * @return {@link Boolean}
1264     */
1265    public static Boolean isAir(String symbolID)
1266    {
1267        int ss = SymbolID.getSymbolSet(symbolID);
1268        int entity = SymbolID.getEntity(symbolID);
1269        if(ss == SymbolID.SymbolSet_Air ||
1270                ss == SymbolID.SymbolSet_AirMissile ||
1271                ss == SymbolID.SymbolSet_SignalsIntelligence_Air)
1272            return true;
1273        else
1274            return false;
1275    }
1276
1277    /**
1278     * Returns true if the symbol is from a space based symbol set
1279     * @param symbolID 30 Character {@link String}
1280     * @return {@link Boolean}
1281     */
1282    public static Boolean isSpace(String symbolID)
1283    {
1284        int ss = SymbolID.getSymbolSet(symbolID);
1285        int entity = SymbolID.getEntity(symbolID);
1286        if(ss == SymbolID.SymbolSet_Space ||
1287                ss == SymbolID.SymbolSet_SpaceMissile ||
1288                ss == SymbolID.SymbolSet_SignalsIntelligence_Space)
1289            return true;
1290        else
1291            return false;
1292    }
1293
1294    /**
1295     * Returns true if the symbol is from a land based symbol set
1296     * @param symbolID 30 Character {@link String}
1297     * @return {@link Boolean}
1298     */
1299    public static Boolean isLand(String symbolID)
1300    {
1301        int ss = SymbolID.getSymbolSet(symbolID);
1302        int entity = SymbolID.getEntity(symbolID);
1303        if(ss == SymbolID.SymbolSet_LandUnit ||
1304                ss == SymbolID.SymbolSet_LandCivilianUnit_Organization ||
1305                ss == SymbolID.SymbolSet_LandEquipment ||
1306                ss == SymbolID.SymbolSet_LandInstallation ||
1307                ss == SymbolID.SymbolSet_SignalsIntelligence_Land)
1308            return true;
1309        else
1310            return false;
1311    }
1312
1313    /**
1314     * Returns true if the symbol ID has the task for indicator
1315     * @param symbolID 30 Character {@link String}
1316     * @return {@link Boolean}
1317     */
1318    public static Boolean isTaskForce(String symbolID)
1319    {
1320        int hqtfd = SymbolID.getHQTFD(symbolID);
1321        if((hqtfd == SymbolID.HQTFD_TaskForce ||
1322                hqtfd == SymbolID.HQTFD_TaskForce_Headquarters ||
1323                hqtfd == SymbolID.HQTFD_FeintDummy_TaskForce ||
1324                hqtfd == SymbolID.HQTFD_FeintDummy_TaskForce_Headquarters) &&
1325                SymbolUtilities.canSymbolHaveModifier(symbolID, Modifiers.B_ECHELON))
1326            return true;
1327        else
1328            return false;
1329    }
1330
1331    /**
1332     * Returns true if the symbol ID indicates the context is Reality
1333     * @param symbolID 30 Character {@link String}
1334     * @return {@link Boolean}
1335     */
1336    public static Boolean isReality(String symbolID)
1337    {
1338        int c = SymbolID.getContext(symbolID);
1339        if(c == SymbolID.StandardIdentity_Context_Reality ||
1340                c == 3 || c == 4)
1341            return true;
1342        else
1343            return false;
1344    }
1345
1346    /**
1347     * Returns true if the symbol ID indicates the context is Exercise
1348     * @param symbolID 30 Character {@link String}
1349     * @return {@link Boolean}
1350     */
1351    public static Boolean isExercise(String symbolID)
1352    {
1353        int c = SymbolID.getContext(symbolID);
1354        if(c == SymbolID.StandardIdentity_Context_Exercise ||
1355                c == 5 || c == 6)
1356            return true;
1357        else
1358            return false;
1359    }
1360
1361    /**
1362     * Returns true if the symbol ID indicates the context is Simulation
1363     * @param symbolID 30 Character {@link String}
1364     * @return {@link Boolean}
1365     */
1366    public static Boolean isSimulation(String symbolID)
1367    {
1368        int c = SymbolID.getContext(symbolID);
1369        if(c == SymbolID.StandardIdentity_Context_Simulation ||
1370                c == 7 || c == 8)
1371            return true;
1372        else
1373            return false;
1374    }
1375
1376
1377    /**
1378     * Reads the Symbol ID string and returns the text that represents the echelon
1379     * code.
1380     * @param echelon {@link Integer} from positions 9-10 in the symbol ID
1381     * See {@link SymbolID#getAmplifierDescriptor(String)}
1382     * @return {@link String} (23 (Army) would be "XXXX")
1383     */
1384    public static String getEchelonText(int echelon)
1385    {
1386        char[] dots = new char[3];
1387        dots[0] = (char)8226;
1388        dots[1] = (char)8226;
1389        dots[2] = (char)8226;
1390        String dot = new String(dots);
1391        String text = null;
1392        if(echelon == SymbolID.Echelon_Team_Crew)
1393        {
1394            text = (char) 216 + "";
1395        }
1396        else if(echelon == SymbolID.Echelon_Squad)
1397        {
1398            text = dot.substring(0, 1);
1399        }
1400        else if(echelon == SymbolID.Echelon_Section)
1401        {
1402            text = dot.substring(0, 2);
1403        }
1404        else if(echelon == SymbolID.Echelon_Platoon_Detachment)
1405        {
1406            text = dot;
1407        }
1408        else if(echelon == SymbolID.Echelon_Company_Battery_Troop)
1409        {
1410            text = "I";
1411        }
1412        else if(echelon == SymbolID.Echelon_Battalion_Squadron)
1413        {
1414            text = "II";
1415        }
1416        else if(echelon == SymbolID.Echelon_Regiment_Group)
1417        {
1418            text = "III";
1419        }
1420        else if(echelon == SymbolID.Echelon_Brigade)
1421        {
1422            text = "X";
1423        }
1424        else if(echelon == SymbolID.Echelon_Division)
1425        {
1426            text = "XX";
1427        }
1428        else if(echelon == SymbolID.Echelon_Corps_MEF)
1429        {
1430            text = "XXX";
1431        }
1432        else if(echelon == SymbolID.Echelon_Army)
1433        {
1434            text = "XXXX";
1435        }
1436        else if(echelon == SymbolID.Echelon_ArmyGroup_Front)
1437        {
1438            text = "XXXXX";
1439        }
1440        else if(echelon == SymbolID.Echelon_Region_Theater)
1441        {
1442            text = "XXXXXX";
1443        }
1444        else if(echelon == SymbolID.Echelon_Region_Command)
1445        {
1446            text = "++";
1447        }
1448        return text;
1449    }
1450
1451    /**
1452     * Returns the Standard Identity Modifier based on the Symbol ID
1453     * @param symbolID 30 Character {@link String}
1454     * @return {@link String}
1455     */
1456    public static String getStandardIdentityModifier(String symbolID)
1457    {
1458        String textChar = null;
1459        int si = SymbolID.getStandardIdentity(symbolID);
1460        int context = SymbolID.getContext(symbolID);
1461        int affiliation = SymbolID.getAffiliation(symbolID);
1462
1463        if(context == SymbolID.StandardIdentity_Context_Simulation)//Simulation
1464            textChar = "S";
1465        else if(context == SymbolID.StandardIdentity_Context_Exercise)
1466        {
1467            if(affiliation == SymbolID.StandardIdentity_Affiliation_Suspect_Joker)//exercise Joker
1468                textChar = "J";
1469            else if(affiliation == SymbolID.StandardIdentity_Affiliation_Hostile_Faker)//exercise faker
1470                textChar = "K";
1471            else if(context == SymbolID.StandardIdentity_Context_Exercise)//exercise
1472                textChar = "X";
1473        }
1474
1475        return textChar;
1476    }
1477
1478    /**
1479     *
1480     * @param symbolID
1481     * @return
1482     */
1483    public static boolean hasRectangleFrame(String symbolID)
1484    {
1485        int affiliation = SymbolID.getAffiliation(symbolID);
1486        int ss = SymbolID.getSymbolSet(symbolID);
1487        if(ss != SymbolID.SymbolSet_ControlMeasure)
1488        {
1489            if (affiliation == SymbolID.StandardIdentity_Affiliation_Friend
1490                    || affiliation == SymbolID.StandardIdentity_Affiliation_AssumedFriend
1491                    || (SymbolID.getContext(symbolID)==SymbolID.StandardIdentity_Context_Exercise &&
1492                    (affiliation == SymbolID.StandardIdentity_Affiliation_Hostile_Faker
1493                            || affiliation == SymbolID.StandardIdentity_Affiliation_Suspect_Joker)))
1494            {
1495                return true;
1496            }
1497            else
1498                return false;
1499        }
1500        else
1501            return false;
1502    }
1503
1504    /**
1505     * Returns the height ratio for the unit specified by the symbol ID
1506     * Based on Figure 4 in 2525E.
1507     * @param symbolID 30 Character {@link String}
1508     * @return {@link Float}
1509     */
1510    public static float getUnitRatioHeight(String symbolID)
1511    {
1512        int ver = SymbolID.getVersion(symbolID);
1513        int aff = SymbolID.getAffiliation(symbolID);
1514
1515        float rh = 0;
1516
1517        if(ver < SymbolID.Version_2525E)
1518        {
1519            int ss = SymbolID.getSymbolSet(symbolID);
1520
1521            if(aff == SymbolID.StandardIdentity_Affiliation_Hostile_Faker ||
1522                    aff == SymbolID.StandardIdentity_Affiliation_Suspect_Joker)
1523            {
1524                switch (ss){
1525                    case SymbolID.SymbolSet_LandCivilianUnit_Organization:
1526                    case SymbolID.SymbolSet_LandUnit:
1527                    case SymbolID.SymbolSet_LandInstallation:
1528                    case SymbolID.SymbolSet_LandEquipment:
1529                    case SymbolID.SymbolSet_SignalsIntelligence_Land:
1530                    case SymbolID.SymbolSet_Activities:
1531                    case SymbolID.SymbolSet_CyberSpace:
1532                        rh = 1.44f;
1533                        break;
1534                    default:
1535                        rh=1.3f;
1536                }
1537            }
1538            else if(aff == SymbolID.StandardIdentity_Affiliation_Friend ||
1539                    aff == SymbolID.StandardIdentity_Affiliation_AssumedFriend)
1540            {
1541                switch (ss){
1542                    case SymbolID.SymbolSet_LandCivilianUnit_Organization:
1543                    case SymbolID.SymbolSet_LandUnit:
1544                    case SymbolID.SymbolSet_LandInstallation:
1545                    case SymbolID.SymbolSet_Activities:
1546                    case SymbolID.SymbolSet_CyberSpace:
1547                        rh = 1f;
1548                        break;
1549                    case SymbolID.SymbolSet_SignalsIntelligence_Land:
1550                    default:
1551                        rh=1.2f;
1552                }
1553            }
1554            else if(aff == SymbolID.StandardIdentity_Affiliation_Neutral)
1555            {
1556                switch (ss){
1557                    case SymbolID.SymbolSet_LandCivilianUnit_Organization:
1558                    case SymbolID.SymbolSet_LandUnit:
1559                    case SymbolID.SymbolSet_LandInstallation:
1560                    case SymbolID.SymbolSet_LandEquipment:
1561                    case SymbolID.SymbolSet_SignalsIntelligence_Land:
1562                    case SymbolID.SymbolSet_Activities:
1563                    case SymbolID.SymbolSet_CyberSpace:
1564                        rh = 1.1f;
1565                        break;
1566                    default:
1567                        rh=1.2f;
1568                }
1569            }
1570            else //UNKNOWN
1571            {
1572                switch (ss){
1573                    case SymbolID.SymbolSet_LandCivilianUnit_Organization:
1574                    case SymbolID.SymbolSet_LandUnit:
1575                    case SymbolID.SymbolSet_LandInstallation:
1576                    case SymbolID.SymbolSet_LandEquipment:
1577                    case SymbolID.SymbolSet_SignalsIntelligence_Land:
1578                    case SymbolID.SymbolSet_Activities:
1579                    case SymbolID.SymbolSet_CyberSpace:
1580                        rh = 1.44f;
1581                        break;
1582                    default:
1583                        rh=1.3f;
1584                }
1585            }
1586        }
1587        else //2525E and up
1588        {
1589            String frameID = SVGLookup.getFrameID(symbolID);
1590            if(frameID.length()==6)
1591                aff = Integer.parseInt(frameID.substring(2,3));
1592            else //"octagon"
1593                return 1f;
1594            char fs = (frameID.charAt(3));
1595
1596            if(aff == SymbolID.StandardIdentity_Affiliation_Hostile_Faker ||
1597                    aff == SymbolID.StandardIdentity_Affiliation_Suspect_Joker)
1598            {
1599                switch (fs){
1600                    case SymbolID.FrameShape_LandUnit:
1601                    case SymbolID.FrameShape_LandInstallation:
1602                    case SymbolID.FrameShape_LandEquipment:
1603                    case SymbolID.FrameShape_SeaSurface:
1604                    case SymbolID.FrameShape_Activity_Event:
1605                    case SymbolID.FrameShape_Cyberspace:
1606                        rh = 1.44f;
1607                        break;
1608                    default:
1609                        rh=1.3f;
1610                }
1611            }
1612            else if(aff == SymbolID.StandardIdentity_Affiliation_Friend ||
1613                    aff == SymbolID.StandardIdentity_Affiliation_AssumedFriend)
1614            {
1615                switch (fs){
1616                    case SymbolID.FrameShape_LandUnit:
1617                    case SymbolID.FrameShape_LandInstallation:
1618                    case SymbolID.FrameShape_Activity_Event:
1619                    case SymbolID.FrameShape_Cyberspace:
1620                        rh = 1f;
1621                        break;
1622                    default:
1623                        rh=1.2f;
1624                }
1625            }
1626            else if(aff == SymbolID.StandardIdentity_Affiliation_Neutral)
1627            {
1628                switch (fs){
1629                    case SymbolID.FrameShape_LandUnit:
1630                    case SymbolID.FrameShape_LandInstallation:
1631                    case SymbolID.FrameShape_LandEquipment:
1632                    case SymbolID.FrameShape_SeaSurface:
1633                    case SymbolID.FrameShape_Activity_Event:
1634                    case SymbolID.FrameShape_Cyberspace:
1635                        rh = 1.1f;
1636                        break;
1637                    default:
1638                        rh=1.2f;
1639                }
1640            }
1641            else //UNKNOWN
1642            {
1643                switch (fs){
1644                    case SymbolID.FrameShape_LandUnit:
1645                    case SymbolID.FrameShape_LandInstallation:
1646                    case SymbolID.FrameShape_LandEquipment:
1647                    case SymbolID.FrameShape_SeaSurface:
1648                    case SymbolID.FrameShape_Activity_Event:
1649                    case SymbolID.FrameShape_Cyberspace:
1650                        rh = 1.44f;
1651                        break;
1652                    default:
1653                        rh=1.3f;
1654                }
1655            }
1656
1657
1658        }
1659
1660        return rh;
1661    }
1662
1663    /**
1664     * Returns the width ratio for the unit specified by the symbol ID
1665     * Based on Figure 4 in 2525E.
1666     * @param symbolID 30 Character {@link String}
1667     * @return {@link Float}
1668     */
1669    public static float getUnitRatioWidth(String symbolID)
1670    {
1671        int ver = SymbolID.getVersion(symbolID);
1672        int aff = SymbolID.getAffiliation(symbolID);
1673
1674        float rw = 0;
1675
1676        if(ver < SymbolID.Version_2525E)
1677        {
1678            int ss = SymbolID.getSymbolSet(symbolID);
1679
1680            if(aff == SymbolID.StandardIdentity_Affiliation_Hostile_Faker ||
1681                    aff == SymbolID.StandardIdentity_Affiliation_Suspect_Joker)
1682            {
1683                switch (ss){
1684                    case SymbolID.SymbolSet_LandCivilianUnit_Organization:
1685                    case SymbolID.SymbolSet_LandUnit:
1686                    case SymbolID.SymbolSet_LandInstallation:
1687                    case SymbolID.SymbolSet_LandEquipment:
1688                    case SymbolID.SymbolSet_SignalsIntelligence_Land:
1689                    case SymbolID.SymbolSet_Activities:
1690                    case SymbolID.SymbolSet_CyberSpace:
1691                        rw = 1.44f;
1692                        break;
1693                    default:
1694                        rw=1.1f;
1695                }
1696            }
1697            else if(aff == SymbolID.StandardIdentity_Affiliation_Friend ||
1698                    aff == SymbolID.StandardIdentity_Affiliation_AssumedFriend)
1699            {
1700                switch (ss){
1701                    case SymbolID.SymbolSet_LandCivilianUnit_Organization:
1702                    case SymbolID.SymbolSet_LandUnit:
1703                    case SymbolID.SymbolSet_LandInstallation:
1704                    case SymbolID.SymbolSet_Activities:
1705                    case SymbolID.SymbolSet_CyberSpace:
1706                        rw = 1.5f;
1707                        break;
1708                    case SymbolID.SymbolSet_LandEquipment:
1709                    case SymbolID.SymbolSet_SignalsIntelligence_Land:
1710                        rw = 1.2f;
1711                        break;
1712                    default:
1713                        rw=1.1f;
1714                }
1715            }
1716            else if(aff == SymbolID.StandardIdentity_Affiliation_Neutral)
1717            {
1718                rw = 1.1f;
1719            }
1720            else //UNKNOWN
1721            {
1722                switch (ss){
1723                    case SymbolID.SymbolSet_LandCivilianUnit_Organization:
1724                    case SymbolID.SymbolSet_LandUnit:
1725                    case SymbolID.SymbolSet_LandInstallation:
1726                    case SymbolID.SymbolSet_LandEquipment:
1727                    case SymbolID.SymbolSet_SignalsIntelligence_Land:
1728                    case SymbolID.SymbolSet_Activities:
1729                    case SymbolID.SymbolSet_CyberSpace:
1730                        rw = 1.44f;
1731                        break;
1732                    default:
1733                        rw=1.5f;
1734                }
1735            }
1736        }
1737        else //2525E and above
1738        {
1739            String frameID = SVGLookup.getFrameID(symbolID);
1740            if(frameID.length()==6)
1741                aff = Integer.parseInt(frameID.substring(2,3));
1742            else //"octagon"
1743                return 1f;
1744            char fs = (frameID.charAt(3));
1745
1746            if(aff == SymbolID.StandardIdentity_Affiliation_Hostile_Faker ||
1747                    aff == SymbolID.StandardIdentity_Affiliation_Suspect_Joker)
1748            {
1749                switch (fs){
1750                    case SymbolID.FrameShape_LandUnit:
1751                    case SymbolID.FrameShape_LandInstallation:
1752                    case SymbolID.FrameShape_LandEquipment:
1753                    case SymbolID.FrameShape_SeaSurface:
1754                    case SymbolID.FrameShape_Activity_Event:
1755                    case SymbolID.FrameShape_Cyberspace:
1756                        rw = 1.44f;
1757                        break;
1758                    default:
1759                        rw=1.1f;
1760                }
1761            }
1762            else if(aff == SymbolID.StandardIdentity_Affiliation_Friend ||
1763                    aff == SymbolID.StandardIdentity_Affiliation_AssumedFriend)
1764            {
1765                switch (fs){
1766                    case SymbolID.FrameShape_LandUnit:
1767                    case SymbolID.FrameShape_LandInstallation:
1768                    case SymbolID.FrameShape_Activity_Event:
1769                    case SymbolID.FrameShape_Cyberspace:
1770                        rw = 1.5f;
1771                        break;
1772                    case SymbolID.FrameShape_LandEquipment:
1773                    case SymbolID.FrameShape_SeaSurface:
1774                        rw = 1.2f;
1775                        break;
1776                    default:
1777                        rw=1.1f;
1778                }
1779            }
1780            else if(aff == SymbolID.StandardIdentity_Affiliation_Neutral)
1781            {
1782                rw = 1.1f;
1783            }
1784            else //UNKNOWN
1785            {
1786                switch (fs){
1787                    case SymbolID.FrameShape_LandUnit:
1788                    case SymbolID.FrameShape_LandInstallation:
1789                    case SymbolID.FrameShape_LandEquipment:
1790                    case SymbolID.FrameShape_SeaSurface:
1791                    case SymbolID.FrameShape_Activity_Event:
1792                    case SymbolID.FrameShape_Cyberspace:
1793                        rw = 1.44f;
1794                        break;
1795                    default:
1796                        rw=1.5f;
1797                }
1798            }
1799        }
1800
1801        return rw;
1802    }
1803
1804    /**
1805     * @param linetype the line type
1806     * @return true if the line is a basic shape
1807     */
1808    public static boolean isBasicShape(int linetype) {
1809        switch (linetype) {
1810            case TacticalLines.BS_AREA:
1811            case TacticalLines.BS_LINE:
1812            case TacticalLines.BS_CROSS:
1813            case TacticalLines.BS_ELLIPSE:
1814            case TacticalLines.PBS_ELLIPSE:
1815            case TacticalLines.PBS_CIRCLE:
1816            case TacticalLines.PBS_SQUARE:
1817            case TacticalLines.PBS_RECTANGLE:
1818            case TacticalLines.BS_RECTANGLE:
1819            case TacticalLines.BBS_AREA:
1820            case TacticalLines.BBS_LINE:
1821            case TacticalLines.BBS_POINT:
1822            case TacticalLines.BBS_RECTANGLE:
1823            case TacticalLines.BS_BBOX:
1824            case TacticalLines.BS_ROUTE:
1825            case TacticalLines.BS_TRACK:
1826            case TacticalLines.BS_RADARC:
1827            case TacticalLines.BS_CAKE:
1828            case TacticalLines.BS_ORBIT:
1829            case TacticalLines.BS_POLYARC:
1830                return true;
1831            default:
1832                return false;
1833        }
1834    }
1835}