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