001package sec.web.render;
002// This import is if we need to call a javascript function
003// It requires that you import the plugins.jar from the jdk folder into the project libraries
004//import netscape.javascript.JSObject;
005
006import android.content.Context;
007import android.graphics.BitmapShader;
008import android.graphics.Shader;
009import android.util.SparseArray;
010import armyc2.c2sd.renderer.MilStdIconRenderer;
011import armyc2.c2sd.renderer.PatternFillRenderer;
012import armyc2.c2sd.renderer.utilities.ErrorLogger;
013import armyc2.c2sd.renderer.utilities.MilStdAttributes;
014import armyc2.c2sd.renderer.utilities.MilStdSymbol;
015import armyc2.c2sd.renderer.utilities.ModifiersTG;
016import armyc2.c2sd.renderer.utilities.RendererSettings;
017import armyc2.c2sd.renderer.utilities.ShapeInfo;
018import armyc2.c2sd.renderer.utilities.SymbolUtilities;
019import armyc2.c2sd.renderer.utilities.Color;
020import armyc2.c2sd.graphics2d.*;
021import sec.web.render.utilities.JavaRendererUtilities;
022import java.util.logging.Level;
023import sec.geo.kml.KmlOptions;
024import sec.web.json.utilities.JSONArray;
025import sec.web.json.utilities.JSONException;
026import sec.web.json.utilities.JSONObject;
027import sec.geo.utilities.StringBuilder;
028import java.util.ArrayList;
029
030/**
031 *
032 * @author Administrator
033 */
034//@SuppressWarnings("unused")
035public final class SECWebRenderer /* extends Applet */ {
036        private static final long serialVersionUID = -2691218568602318366L;
037        
038        // constants for the available shape types that can be generated into KML
039    public static final String CYLINDER = "CYLINDER-------";
040    public static final String ORBIT = "ORBIT----------";
041    public static final String ROUTE = "ROUTE----------";
042    public static final String POLYGON = "POLYGON--------";
043    public static final String RADARC = "RADARC---------";
044    public static final String POLYARC = "POLYARC--------";
045    public static final String CAKE = "CAKE-----------";
046    public static final String TRACK = "TRACK----------";
047    // Attribute names of the 3D shapes
048    public static final String ATTRIBUTES = "attributes";
049    public static final String MIN_ALT = "minalt";
050    public static final String MAX_ALT = "maxalt";
051    public static final String RADIUS1 = "radius1";
052    public static final String RADIUS2 = "radius2";
053    public static final String LEFT_AZIMUTH = "leftAzimuth";
054    public static final String RIGHT_AZIMUTH = "rightAzimuth";
055    // Arbitrary default values of attributes
056    public static final double MIN_ALT_DEFAULT = 0.0D;
057    public static final double MAX_ALT_DEFAULT = 100.0D;
058    public static final double RADIUS1_DEFAULT = 50.0D;
059    public static final double RADIUS2_DEFAULT = 100.0D;
060    public static final double LEFT_AZIMUTH_DEFAULT = 0.0D;
061    public static final double RIGHT_AZIMUTH_DEFAULT = 90.0D;
062    
063    public static final String ERR_ATTRIBUTES_NOT_FORMATTED = "{\"type\":\"error\","
064            + "\"error\":\"The attribute paramaters are not formatted "
065            + "correctly";
066    
067    public static final String DEFAULT_ATTRIBUTES = "[{radius1:"
068            + RADIUS1_DEFAULT + ",radius2:"
069            + RADIUS2_DEFAULT + ",minalt:"
070            + MIN_ALT_DEFAULT + ",maxalt:"
071            + MAX_ALT_DEFAULT + ",rightAzimuth:"
072            + RIGHT_AZIMUTH_DEFAULT + ",leftAzimuth:"
073            + LEFT_AZIMUTH_DEFAULT + "}]";
074
075    
076    private static boolean _initSuccess = false;
077    
078
079    public static synchronized void init(Context context, String cacheDir) {
080
081        try
082        {
083                if(_initSuccess == false)
084                {
085                    MilStdIconRenderer.getInstance().init(context, cacheDir);
086                    //use SECWebRenderer.setLoggingLevel()
087                    
088                    //sets default value for single point symbology to have an outline.
089                    //outline color will be automatically determined based on line color
090                    //unless a color value is manually set.
091                    
092                    //Set Renderer Settings/////////////////////////////////////////////
093                    RendererSettings.getInstance().setSinglePointSymbolOutlineWidth(1);
094                    RendererSettings.getInstance().setTextRenderMethod(RendererSettings.RenderMethod_NATIVE);
095                    RendererSettings.getInstance().setTextBackgroundMethod(
096                                    RendererSettings.TextBackgroundMethod_OUTLINE_QUICK);
097                    RendererSettings.getInstance().setTextOutlineWidth(2);
098                    RendererSettings.getInstance().setLabelForegroundColor(Color.BLACK.toARGB());
099                    RendererSettings.getInstance().setLabelBackgroundColor(new Color(255, 255, 255, 200).toARGB());
100                    RendererSettings.getInstance().setModifierFont("arial", Font.PLAIN, 12);
101                    ErrorLogger.setLevel(Level.FINE);
102                    _initSuccess = true;
103                }
104            
105        }
106        catch(Exception exc)
107        {
108            ErrorLogger.LogException("SECWebRenderer", "init", exc, Level.WARNING);
109        }
110
111
112    }
113
114    
115    /**\
116     * Set minimum level at which an item can be logged.
117     * In descending order:
118     * OFF = Integer.MAX_VALUE
119     * Severe = 1000
120     * Warning = 900
121     * Info = 800
122     * Config = 700
123     * Fine = 500
124     * Finer = 400 
125     * Finest = 300
126     * All = Integer.MIN_VALUE
127     * Use like SECWebRenderer.setLoggingLevel(Level.INFO);
128     * or
129     * Use like SECWebRenderer.setLoggingLevel(800);
130     * @param level java.util.logging.level
131     */
132    public static void setLoggingLevel(Level level)
133    {
134        try
135        {
136            ErrorLogger.setLevel(level,true);
137            ErrorLogger.LogMessage("SECWebRenderer", "setLoggingLevel(Level)", 
138                    "Logging level set to: " + ErrorLogger.getLevel().getName(), 
139                    Level.CONFIG);
140        }
141        catch(Exception exc)
142        {
143            ErrorLogger.LogException("SECWebRenderer", "setLoggingLevel(Level)", exc, Level.INFO);
144        }
145    }
146    
147    /**\
148     * Set minimum level at which an item can be logged.
149     * In descending order:
150     * OFF = Integer.MAX_VALUE
151     * Severe = 1000
152     * Warning = 900
153     * Info = 800
154     * Config = 700
155     * Fine = 500
156     * Finer = 400 
157     * Finest = 300
158     * All = Integer.MIN_VALUE
159     * Use like SECWebRenderer.setLoggingLevel(Level.INFO);
160     * or
161     * Use like SECWebRenderer.setLoggingLevel(800);
162     * @param level int
163     */
164    public static void setLoggingLevel(int level)
165    {
166        try
167        {
168            if(level > 1000)
169                  ErrorLogger.setLevel(Level.OFF,true);
170            else if(level > 900)
171                  ErrorLogger.setLevel(Level.SEVERE,true);
172            else if(level > 800)
173                  ErrorLogger.setLevel(Level.WARNING,true);
174            else if(level > 700)
175                  ErrorLogger.setLevel(Level.INFO,true);
176            else if(level > 500)
177                  ErrorLogger.setLevel(Level.CONFIG,true);
178            else if(level > 400)
179                  ErrorLogger.setLevel(Level.FINE,true);
180            else if(level > 300)
181                  ErrorLogger.setLevel(Level.FINER,true);
182            else if(level > Integer.MIN_VALUE)
183                  ErrorLogger.setLevel(Level.FINEST,true);
184            else
185                ErrorLogger.setLevel(Level.ALL,true);
186            
187            ErrorLogger.LogMessage("SECWebRenderer", "setLoggingLevel(int)", 
188                    "Logging level set to: " + ErrorLogger.getLevel().getName(), 
189                    Level.CONFIG);
190        }
191        catch(Exception exc)
192        {
193            ErrorLogger.LogException("SECWebRenderer", "setLoggingLevel(int)", exc, Level.INFO);
194        }
195    }
196    
197    /**
198     * Let's user choose between 2525Bch2 and 2525C.
199     * Ideally, set only once at startup.
200     * 2525Bch2 = 0, 2525C = 1.
201     * @param symStd 
202     */
203    public static void setDefaultSymbologyStandard(int symStd)
204    {
205        RendererSettings.getInstance().setSymbologyStandard(symStd);
206    }
207    
208    /**
209     * Single Point Tactical Graphics are rendered from font files.
210     * The font size you specify here determines how big the symbols will 
211     * be rendered.  This should be set once at startup.
212     * @param size 
213     */
214    public static void setTacticalGraphicPointSize(int size)
215    {
216//        sps.setTacticalGraphicPointSize(size);
217    }
218    
219    /**
220     * Units are rendered from font files.
221     * The font size you specify here determines how big the symbols will 
222     * be rendered.  This should be set once at startup. 
223     * @param size 
224     */
225    public static void setUnitPointSize(int size)
226    {
227//        sps.setUnitPointSize(size);
228    }
229    
230    /**
231     * Modifier Text Color will by default match the line color.
232     * This will override all modifier text color.
233     * @param hexColor 
234     */
235    public static void setModifierTextColor(String hexColor)
236    {
237        Color textColor = SymbolUtilities.getColorFromHexString(hexColor);
238        if(textColor==null)
239        {
240            textColor = Color.black;
241        }
242        RendererSettings.getInstance().setLabelForegroundColor(textColor.toARGB());
243    }
244
245
246    
247
248
249    /**
250     * Renders all multi-point symbols, creating KML that can be used to draw
251     * it on a Google map.  Multipoint symbols cannot be draw the same 
252     * at different scales. For instance, graphics with arrow heads will need to 
253     * redraw arrowheads when you zoom in on it.  Similarly, graphics like a 
254     * Forward Line of Troops drawn with half circles can improve performance if 
255     * clipped when the parts of the graphic that aren't on the screen.  To help 
256     * readjust graphics and increase performance, this function requires the 
257     * scale and bounding box to help calculate the new locations.
258     * @param id A unique identifier used to identify the symbol by Google map. 
259     * The id will be the folder name that contains the graphic.
260     * @param name a string used to display to the user as the name of the 
261     * graphic being created.
262     * @param description a brief description about the graphic being made and 
263     * what it represents.
264     * @param symbolCode A 15 character symbolID corresponding to one of the
265     * graphics in the MIL-STD-2525C
266     * @param controlPoints The vertices of the graphics that make up the
267     * graphic.  Passed in the format of a string, using decimal degrees 
268     * separating lat and lon by a comma, separating coordinates by a space.  
269     * The following format shall be used "x1,y1[,z1] [xn,yn[,zn]]..."
270     * @param altitudeMode Indicates whether the symbol should interpret 
271     * altitudes as above sea level or above ground level. Options are 
272     * "clampToGround", "relativeToGround" (from surface of earth), "absolute" 
273     * (sea level), "relativeToSeaFloor" (from the bottom of major bodies of 
274     * water).
275     * @param scale A number corresponding to how many meters one meter of our 
276     * map represents. A value "50000" would mean 1:50K which means for every 
277     * meter of our map it represents 50000 meters of real world distance.
278     * @param bbox The viewable area of the map.  Passed in the format of a
279     * string "lowerLeftX,lowerLeftY,upperRightX,upperRightY." Not required
280     * but can speed up rendering in some cases.
281     * example: "-50.4,23.6,-42.2,24.2"
282     * @param modifiers SparseArray<String>, keyed using constants from ModifiersTG.  
283     * Pass in comma delimited String for modifiers with multiple values like AM, AN & X
284     * @param attributes SparseArray<String>, keyed using constants from MilStdAttributes.  
285     * @param format An enumeration: 0 for KML, 1 for JSON.
286     * @param symStd An enumeration: 0 for 2525Bch2, 1 for 2525C.
287     * @return A JSON string representation of the graphic.
288     */
289    public static String RenderSymbol(String id, String name, String description, 
290            String symbolCode, String controlPoints, String altitudeMode,
291            double scale, String bbox, SparseArray<String> modifiers, SparseArray<String> attributes,  int format, int symStd) {
292        
293        String output = "";
294        try {         
295                
296                JavaRendererUtilities.addAltModeToModifiersString(attributes,altitudeMode);
297        
298            //if (JavaRendererUtilities.is3dSymbol(symbolCode, modifiers))
299            if (altitudeMode.equals("clampToGround") == false && format == 0 && JavaRendererUtilities.is3dSymbol(symbolCode, modifiers))
300            {
301                if (altitudeMode.isEmpty())
302                    altitudeMode = "absolute";
303        
304                output = RenderMilStd3dSymbol(name, id, symbolCode, description, altitudeMode, controlPoints,
305                        modifiers, attributes);
306                
307                //get modifiers/////////////////////////////////////////////////
308                String modifierKML = MultiPointHandler.getModififerKML(id, name, description, symbolCode, controlPoints,
309                        scale, bbox, modifiers, attributes, format,symStd);
310
311                modifierKML += "</Folder>";
312
313                output = output.replaceFirst("</Folder>", modifierKML);
314
315                ////////////////////////////////////////////////////////////////
316                
317                
318                
319                // Check the output of the 3D Symbol Drawing.  If this returned an error
320                // it should either be "" or it should be a JSON string starting with "{".
321                // This really is not a good solution, but was up to 13.0.6 and had to make
322                // this bug fix in quick turnaround.  More consistent error handling should
323                // be done through code.
324               
325                if (output.equals("") || output.startsWith("{")) {
326                    output = MultiPointHandler.RenderSymbol(id, name, description, symbolCode, controlPoints,
327                        scale, bbox, modifiers, attributes, format,symStd);
328                }
329            }
330            else
331            {            
332                output = MultiPointHandler.RenderSymbol(id, name, description, symbolCode, controlPoints,
333                        scale, bbox, modifiers, attributes, format,symStd);
334                
335                //DEBUGGING
336                if(ErrorLogger.getLevel().intValue() <= Level.FINER.intValue())
337                {
338                    System.out.println("");
339                    StringBuilder sb = new StringBuilder();
340                    sb.append("\nID: " + id + "\n");
341                    sb.append("Name: " + name + "\n");
342                    sb.append("Description: " + description + "\n");
343                    sb.append("SymbolID: " + symbolCode + "\n");
344                    sb.append("SymStd: " + String.valueOf(symStd) + "\n");
345                    sb.append("Scale: " + String.valueOf(scale) + "\n");
346                    sb.append("BBox: " + bbox + "\n");
347                    sb.append("Coords: " + controlPoints + "\n");
348                    sb.append("Modifiers: " + modifiers + "\n");
349                    ErrorLogger.LogMessage("SECWebRenderer", "RenderSymbol", sb.toString(),Level.FINER);
350                }
351                if(ErrorLogger.getLevel().intValue() <= Level.FINEST.intValue())
352                {
353                    String briefOutput = output.replaceAll("</Placemark>", "</Placemark>\n");
354                    briefOutput = output.replaceAll("(?s)<description[^>]*>.*?</description>", "<description></description>");
355                    ErrorLogger.LogMessage("SECWebRenderer", "RenderSymbol", "Output:\n" + briefOutput,Level.FINEST);
356                }
357            }
358            
359            
360        } catch (Exception ea) {
361            
362            output = "{\"type\":'error',error:'There was an error creating the MilStdSymbol - " + ea.toString() + "'}";
363            ErrorLogger.LogException("SECWebRenderer", "RenderSymbol", ea, Level.WARNING);
364        }
365        
366        return output;
367    }
368    
369
370         
371
372
373    /**
374     * Renders all multi-point symbols, creating KML or JSON for the user to
375     * parse and render as they like.
376     * This function requires the bounding box to help calculate the new
377     * locations.
378     * @param id A unique identifier used to identify the symbol by Google map.
379     * The id will be the folder name that contains the graphic.
380     * @param name a string used to display to the user as the name of the 
381     * graphic being created.
382     * @param description a brief description about the graphic being made and 
383     * what it represents.
384     * @param symbolCode A 15 character symbolID corresponding to one of the
385     * graphics in the MIL-STD-2525C
386     * @param controlPoints The vertices of the graphics that make up the
387     * graphic.  Passed in the format of a string, using decimal degrees
388     * separating lat and lon by a comma, separating coordinates by a space.
389     * The following format shall be used "x1,y1 [xn,yn]..."
390     * @param pixelWidth pixel dimensions of the viewable map area
391     * @param pixelHeight pixel dimensions of the viewable map area
392     * @param bbox The viewable area of the map.  Passed in the format of a
393     * string "lowerLeftX,lowerLeftY,upperRightX,upperRightY."
394     * example: "-50.4,23.6,-42.2,24.2"
395     * @param modifiers SparseArray<String>, keyed using constants from ModifiersTG.  
396     * Pass in comma delimited String for modifiers with multiple values like AM, AN & X
397     * @param attributes SparseArray<String>, keyed using constants from MilStdAttributes.
398     * @param format An enumeration: 0 for KML, 1 for JSON.
399     * @param symStd An enumeration: 0 for 2525Bch2, 1 for 2525C.
400     * @return A JSON (1) or KML (0) string representation of the graphic.
401     */
402    public static String RenderSymbol2D(String id, String name, String description, String symbolCode, String controlPoints,
403            int pixelWidth, int pixelHeight, String bbox, SparseArray<String> modifiers, 
404            SparseArray<String> attributes, int format, int symStd)
405    {
406        String output = "";
407        try
408        {
409            output = MultiPointHandler.RenderSymbol2D(id, name, description, 
410                    symbolCode, controlPoints, pixelWidth, pixelHeight, bbox, 
411                    modifiers, attributes, format, symStd);
412        }
413        catch(Exception exc)
414        {
415            output = "{\"type\":'error',error:'There was an error creating the MilStdSymbol: " + symbolCode + " - " + exc.toString() + "'}";
416        }
417        return output;
418    }
419
420    /**
421     * Creates a 3D symbol to be displayed on some 3D globe surface.  Generates 
422     * Keyhole Markup Language (KML) to return that specifies the points and format of 
423     * the rendering.
424     * <br/>
425     * Control points should be of the format of:
426     * <tr><code>"x,y,z [x,y,z]..."</code></tr>
427     * Attributes should be passed in as a JSON array.  If more than one set of 
428     * parameters are passed in as an array or more than one item, they will map 
429     * to the vertex specified in the control points.  The attributes are
430     * of the format:
431     * <tr><code>{"attributes":[{"<i>attribute1</i>":<i>value</i>,...},{<i>[optional]</i>]}</code></tr>
432     * 
433     * @param name The user displayed name for the symbol.  Users will use this 
434     * to identify with the symbol.
435     * @param id An internally used unique id that developers can use to 
436     * uniquely distinguish this symbol from others.
437     * @param shapeType A 15 character ID of the type of symbol to draw.
438     * @param description A brief description of what the symbol represents.  
439     * Generic text that does not require any format.
440     * @param fillColor The fill color of the graphic
441     * @param altitudeMode Indicates whether the symbol should interpret 
442     * altitudes as above sea level or above ground level. Options are 
443     * "relativeToGround" (from surface of earth), "absolute" (sea level), 
444     * "relativeToSeaFloor" (from the bottom of major bodies of water).
445     * @param controlPoints The vertices of the shape.  The number of required
446     * vertices varies based on the shapeType of the symbol.  The simplest shape 
447     * requires at least one point.  Shapes that require more points than 
448     * required will ignore extra points.  Format for numbers is as follows: 
449     * <br/><br/>
450     * "x,y,z [x,y,z ]..."
451     * @param attributes A JSON  array holding the parameters for the 
452     * shape.  Attributes should be of the following format: <br/><br/>
453     * <tr><code>{"attributes":[{"<i>attribute1</i>":<i>value</i>,...},{<i>[optional]</i>]}</code></tr>
454     * @return A KML string that represents a placemark for the 3D shape
455     */
456    public static String Render3dSymbol(String name, String id, String shapeType, 
457            String description, String lineColor, String fillColor, String altitudeMode, 
458            String controlPoints,
459            String attributes) {
460        
461        String returnValue = "";
462        
463        try {
464            
465            StringBuilder output = new StringBuilder();
466            SymbolModifiers modifiers = new SymbolModifiers();
467            JSONObject attributesJSON;
468                        
469            // Retrieve the attributes from the attributes object
470            // Attributes should be a JSON array string of this format 
471            // { "attributes":[{"radius1":50, "minalt":0, "maxalt:100"}]}
472            // There should only be one item in the JSON array, but if 
473            // there is more items this will ignore them.            
474            //attributes="{attributes:[{radius1:5000, minalt:0, maxalt:100}]}";
475            attributesJSON = new JSONObject(attributes);
476
477            // If no attributes passed in or attributes set to null
478            // default to an empty string.
479            if (attributesJSON == null || attributes.equals("")) {
480                attributesJSON = new JSONObject(DEFAULT_ATTRIBUTES);
481            }
482
483            JSONArray attributesArray = attributesJSON.getJSONArray(ATTRIBUTES);
484            int attributesArrayLength = attributesArray.length();
485            if (attributesArrayLength> 0) {
486                
487                for(int i = 0; i < attributesArrayLength; i++) {
488                
489                    // get the first item in the array and use those parameters.
490                    // if any of the parameters don't exist, it will use defaults.
491                    // Defaults are arbitrary, no reason not to change them.
492                    JSONObject currentAttributeSet = attributesArray.getJSONObject(i);
493
494                    if (currentAttributeSet.has(RADIUS1)) {
495                        modifiers.AM_DISTANCE.add(currentAttributeSet.getDouble(RADIUS1));
496                    }                                    
497
498                    if (currentAttributeSet.has(RADIUS2)) {
499                        modifiers.AM_DISTANCE.add(currentAttributeSet.getDouble(RADIUS2));
500                    }
501
502                    if (currentAttributeSet.has(MIN_ALT)) {
503                        modifiers.X_ALTITUDE_DEPTH.add(currentAttributeSet.getDouble(MIN_ALT));
504                    }
505
506                    if (currentAttributeSet.has(MAX_ALT)) {
507                        modifiers.X_ALTITUDE_DEPTH.add(currentAttributeSet.getDouble(MAX_ALT));
508                    }
509
510                    if (currentAttributeSet.has(LEFT_AZIMUTH)) {
511                        modifiers.AN_AZIMUTH.add(currentAttributeSet.getDouble(LEFT_AZIMUTH));
512                    }
513
514                    if (currentAttributeSet.has(RIGHT_AZIMUTH)) {
515                        modifiers.AN_AZIMUTH.add(currentAttributeSet.getDouble(RIGHT_AZIMUTH));
516                    }
517                }
518            }
519            
520            // Send to the 3D renderer for generating the 3D point and creating
521            // the KML to return.            
522            returnValue = Shape3DHandler.render3dSymbol(name, id, shapeType, 
523                description, lineColor, fillColor, altitudeMode, controlPoints, modifiers);            
524        } 
525        catch (JSONException je) {
526
527            return ERR_ATTRIBUTES_NOT_FORMATTED;
528        }            
529        catch (Exception ea) {
530            ErrorLogger.LogException("SECWebRenderer", "Render3dSymbol()", ea);
531            return "";
532        }            
533        
534        return returnValue;
535    }
536    
537     /**
538     * Creates a 3D symbol from the MilStd2525B USAS or MIL-STD-2525C to be 
539     * displayed on a 3D globe surface.  Only certain symbols from the MIL-STD
540     * can be displayed in 3D.   Most of these are graphics that fall under Fire
541     * Support.  Any graphic that has an X modifier (altitude/depth) should
542     * have a 3D representation.  Generates 
543     * Keyhole Markup Language (KML) to return that 
544     * specifies the points and format of 
545     * the rendering.
546     * <br/>
547     * Control points should be of the format of:
548     * <tr><code>"x,y,z [x,y,z]..."</code></tr>
549     * 
550     * 
551     * @param name The user displayed name for the symbol.  Users will use this 
552     * to identify with the symbol.
553     * @param id An internally used unique id that developers can use to 
554     * uniquely distinguish this symbol from others.
555     * @param symbolCode A 15 character ID of the type of symbol to draw.  Only
556     * symbols with an X modifier from the standard will draw.
557     * @param description A brief description of what the symbol represents.  
558     * Generic text that does not require any format.  
559     * @param altitudeMode Indicates whether the symbol should interpret 
560     * altitudes as above sea level or above ground level. Options are 
561     * "relativeToGround" (from surface of earth), "absolute" (sea level), 
562     * "relativeToSeaFloor" (from the bottom of major bodies of water).
563     * @param controlPoints The vertices of the shape.  The number of required
564     * vertices varies based on the shapeType of the symbol.  The simplest shape 
565     * requires at least one point.  Shapes that require more points than 
566     * required will ignore extra points.  Format for numbers is as follows: 
567     * <br/><br/>
568     * "x,y,z [x,y,z ]..."
569     * @saModifiers SparseArray<String> keyed on ModifiersTG. Only using values AM, AN and X.
570     * Strings should be comma delimited for multiple values.
571     * @saAttributes SparseArray<String> keyed on MilStdAttributes. Only using value FillColor.
572     * @return A KML string that represents a placemark for the 3D shape
573     */
574    private static String RenderMilStd3dSymbol(String name, String id, String symbolCode, 
575            String description, 
576            String altitudeMode,
577            String controlPoints,
578            SparseArray<String> saModifiers,
579            SparseArray<String> saAttributes) {
580               
581        String symbolId = symbolCode.substring(4,10);
582        SymbolModifiers attributes = new SymbolModifiers();
583        String output = "";
584        
585        KmlOptions.AltitudeMode convertedAltitudeMode = KmlOptions.AltitudeMode.RELATIVE_TO_GROUND;
586
587        // Convert altitude mode to an enum that we understand.  If it does not
588        // understand or is "", then convert to ALTITUDE_RELATIVE_TO_GROUND.
589        if (!altitudeMode.equals(""))
590        {
591            convertedAltitudeMode = KmlOptions.AltitudeMode.fromString(altitudeMode);
592        }
593        
594        try
595        {
596        
597            
598            String[] altitudeDepth = null;
599            String[] distance = null;
600            String[] azimuth = null;
601            int altitudeDepthLength = 0;
602            int distanceLength = 0;
603            int azimuthLength = 0;
604            String lineColor = "";
605            String fillColor = "";
606            
607            if (saModifiers.indexOfKey(ModifiersTG.X_ALTITUDE_DEPTH)>=0)
608            {
609                altitudeDepth = saModifiers.get(ModifiersTG.X_ALTITUDE_DEPTH).split(",");
610                altitudeDepthLength = altitudeDepth.length;
611            }
612            
613            if (saModifiers.indexOfKey(ModifiersTG.AN_AZIMUTH)>=0)
614            {
615                azimuth = saModifiers.get(ModifiersTG.AN_AZIMUTH).split(",");
616                azimuthLength = azimuth.length;
617            }
618            
619            if (saModifiers.indexOfKey(ModifiersTG.AM_DISTANCE)>=0)
620            {
621                distance = saModifiers.get(ModifiersTG.AM_DISTANCE).split(",");
622                distanceLength = distance.length;
623            }
624            
625            if (saAttributes.indexOfKey(MilStdAttributes.LineColor)>=0)
626            {
627                lineColor = saAttributes.get(MilStdAttributes.LineColor);
628            }
629            else
630            {   
631                Color c = SymbolUtilities.getFillColorOfAffiliation(symbolCode);
632                lineColor = c.toHexString();
633                // ensure that some color is selected.  If no color can be
634                // found, use black.
635                if (lineColor == null)
636                {
637                        lineColor = "FF000000";
638                }
639            }
640            
641            if (saAttributes.indexOfKey(MilStdAttributes.FillColor)>=0)
642            {
643                fillColor = saAttributes.get(MilStdAttributes.FillColor);
644            }
645            else
646            {   
647                Color c = SymbolUtilities.getFillColorOfAffiliation(symbolCode);
648                fillColor = c.toHexString();
649                // ensure that some color is selected.  If no color can be
650                // found, use black.
651                if (fillColor == null)
652                {
653                        fillColor = "AA000000";
654                }
655            }
656            
657            lineColor = JavaRendererUtilities.ARGBtoABGR(lineColor);
658            fillColor = JavaRendererUtilities.ARGBtoABGR(fillColor);
659                            
660            for (int i=0; i < altitudeDepthLength; i++)
661            {
662                // if it's a killbox, need to set minimum alt to 0.
663                if (symbolId.startsWith("AJP"))
664                {
665                    attributes.X_ALTITUDE_DEPTH.add(0D);
666                    i++;
667                }                                        
668                attributes.X_ALTITUDE_DEPTH.add(Double.parseDouble(altitudeDepth[i]));
669            }
670            for (int i=0; i < distanceLength; i++)
671            {
672                // If this is a 'track' type graphic, then we need to take the distance
673                // and divide it by half, than add it twice.  This is due 
674                // to the TAIS requirement that Tracks must have a left width 
675                // and a right width. 
676                if (symbolId.equals("ACAR--") || // ACA - rectangular
677                    symbolId.equals("AKPR--") || // Killbox - rectangular
678                    symbolId.equals("ALC---") || // air corricor
679                    symbolId.equals("ALM---") || // MRR
680                    symbolId.equals("ALS---") || // SAAFR
681                    symbolId.equals("ALU---") || // unmanned aircraft
682                    symbolId.equals("ALL---")) {  // LLTR) {
683                    double width = Double.parseDouble(distance[i]) / 2;
684                    attributes.AM_DISTANCE.add(width);
685                    attributes.AM_DISTANCE.add(width);
686                } else {
687                    attributes.AM_DISTANCE.add(Double.parseDouble(distance[i]));
688                }
689            }  
690
691            if (symbolId.equals("ACAI--") || // ACA - irregular
692                    symbolId.equals("AKPI--") || // Killbox - irregular
693                    symbolId.equals("AAR---") || // ROZ
694                    symbolId.equals("AAF---") || // SHORADEZ
695                    symbolId.equals("AAH---") || // HIDACZ
696                    symbolId.equals("AAM---") || // MEZ
697                    symbolId.equals("AAML--") || // LOMEZ
698                    symbolId.equals("AAMH--")) // HIMEZ
699            {
700                output = Shape3DHandler.buildPolygon(controlPoints, id, name, 
701                    description, lineColor, fillColor, convertedAltitudeMode, attributes);
702            }
703            else if (symbolId.equals("ACAR--") || // ACA - rectangular
704                    symbolId.equals("AKPR--") || // Killbox - rectangular
705                    symbolId.equals("ALC---") || // air corricor
706                    symbolId.equals("ALM---") || // MRR
707                    symbolId.equals("ALS---") || // SAAFR
708                    symbolId.equals("ALU---") || // unmanned aircraft
709                    symbolId.equals("ALL---"))   // LLTR
710            {
711                output = Shape3DHandler.buildTrack(controlPoints, id, name, 
712                    description, lineColor, fillColor, convertedAltitudeMode, attributes);
713            }
714            else if (symbolId.equals("ACAC--") || // ACA - circular
715                    symbolId.equals("AKPC--"))    // Killbox - circular
716            {
717                output = Shape3DHandler.buildCylinder(controlPoints, id, name, 
718                    description, lineColor, fillColor, convertedAltitudeMode, attributes);
719
720            }   
721
722            
723        }
724        catch (Exception exc)
725        {
726            output = "";
727        } 
728        return output;
729    }
730
731    /**
732         * Renders all MilStd 2525 multi-point symbols, creating MilStdSymbol that contains the
733         * information needed to draw the symbol on the map.
734     * DOES NOT support RADARC, CAKE, TRACK etc...
735         * ArrayList<Point2D> milStdSymbol.getSymbolShapes.get(index).getPolylines()
736         * and 
737         * ShapeInfo = milStdSymbol.getModifierShapes.get(index). 
738         * 
739         * 
740         * @param id
741         *            A unique identifier used to identify the symbol by Google map.
742         *            The id will be the folder name that contains the graphic.
743         * @param name
744         *            a string used to display to the user as the name of the
745         *            graphic being created.
746         * @param description
747         *            a brief description about the graphic being made and what it
748         *            represents.
749         * @param symbolCode
750         *            A 15 character symbolID corresponding to one of the graphics
751         *            in the MIL-STD-2525C
752         * @param controlPoints
753         *            The vertices of the graphics that make up the graphic. Passed
754         *            in the format of a string, using decimal degrees separating
755         *            lat and lon by a comma, separating coordinates by a space. The
756         *            following format shall be used "x1,y1[,z1] [xn,yn[,zn]]..."
757         * @param altitudeMode
758         *            Indicates whether the symbol should interpret altitudes as
759         *            above sea level or above ground level. Options are
760         *            "clampToGround", "relativeToGround" (from surface of earth),
761         *            "absolute" (sea level), "relativeToSeaFloor" (from the bottom
762         *            of major bodies of water).
763         * @param scale
764         *            A number corresponding to how many meters one meter of our map
765         *            represents. A value "50000" would mean 1:50K which means for
766         *            every meter of our map it represents 50000 meters of real
767         *            world distance.
768         * @param bbox
769         *            The viewable area of the map. Passed in the format of a string
770         *            "lowerLeftX,lowerLeftY,upperRightX,upperRightY." Not required
771         *            but can speed up rendering in some cases. example:
772         *            "-50.4,23.6,-42.2,24.2"
773         * @param modifiers
774         *            Used like:
775         *            modifiers.put(ModifiersTG.T_UNIQUE_DESIGNATION_1, "T");
776         *            Or
777         *            modifiers.put(ModifiersTG.AM_DISTANCE, "1000,2000,3000");
778         * @param attributes
779         *                        Used like:
780         *            attributes.put(MilStdAttributes.LineWidth, "3");
781         *            Or
782         *            attributes.put(MilStdAttributes.LineColor, "#00FF00");
783         * @param symStd
784         *            An enumeration: 0 for 2525Bch2, 1 for 2525C.
785     * @return MilStdSymbol
786     */
787    public static MilStdSymbol RenderMultiPointAsMilStdSymbol(String id, String name, String description, String symbolCode,
788                        String controlPoints, String altitudeMode, double scale, String bbox, SparseArray<String> modifiers, SparseArray<String> attributes, int symStd)
789    {
790                MilStdSymbol mSymbol = null;
791                try 
792                {
793                        mSymbol = MultiPointHandler.RenderSymbolAsMilStdSymbol(id, name, description, symbolCode,
794                    controlPoints, scale, bbox, modifiers, attributes, symStd);
795
796            String basicID = SymbolUtilities.getBasicSymbolID(symbolCode);
797            if(basicID.charAt(0)=='G' && ((basicID.charAt(2)=='G' &&  basicID.substring(4,7).equals("PC-")) ||  (basicID.charAt(2)=='M' &&  basicID.substring(4,7).equals("OFD"))))
798            {
799                String A = "G*MPOMU---****X";//unspecified mine, default if not specified
800                if(modifiers.indexOfKey(ModifiersTG.A_SYMBOL_ICON) >= 0)
801                    A = modifiers.get(ModifiersTG.A_SYMBOL_ICON);
802
803                //test
804                //A = "G*MPOMW---****X,G*MPOMD---****X,G*MPOME---****X";
805                //A = "G*MPOMEXXX****X";
806                int size = RendererSettings.getInstance().getDefaultPixelSize();
807
808                ArrayList<ShapeInfo> shapes = mSymbol.getSymbolShapes();
809                if(shapes.size() > 0){
810                    ShapeInfo shape = shapes.get(0);
811                    shape.setPatternFillImage(PatternFillRenderer.MakeSymbolPatternFill(A,size));
812                    if(shape.getPatternFillImage() != null)
813                        shape.setShader(new BitmapShader(shape.getPatternFillImage(), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT));
814                }
815            }
816            else if(basicID.charAt(0) == 'W')
817            {
818                ArrayList<ShapeInfo> shapes = mSymbol.getSymbolShapes();
819                if(shapes.size() > 0)
820                {
821                    ShapeInfo shape = shapes.get(0);
822                    shape.setPatternFillImage(PatternFillRenderer.MakeMetocPatternFill(symbolCode));
823                    if(shape.getPatternFillImage() != null)
824                        shape.setShader(new BitmapShader(shape.getPatternFillImage(), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT));
825                }
826            }
827
828                }
829                catch (Exception ea) 
830                {
831                        mSymbol=null;
832                        ErrorLogger.LogException("SECRenderer", "RenderMultiPointAsMilStdSymbol" + " - " + symbolCode, ea, Level.WARNING);
833                }
834                
835                //System.out.println("RenderMultiPointAsMilStdSymbol exit");
836                return mSymbol;
837    }
838
839
840    
841    /**
842     * Given a symbol code meant for a single point symbol, returns the
843     * anchor point at which to display that image based off the image returned
844     * from the URL of the SinglePointServer.
845     * 
846     * @param symbolID - the 15 character symbolID of a single point MilStd2525
847     * symbol. 
848     * @return A pixel coordinate of the format "x,y".
849     * Returns an empty string if an error occurs.
850     * @deprecated 
851     */
852        public String getSinglePointAnchor(String symbolID) {
853        String anchorPoint = "";
854        Point2D anchor = new Point2D.Double();
855        anchorPoint = anchor.getX() + "," + anchor.getY();        
856        return anchorPoint;
857    }
858
859    /**
860     * Given a symbol code meant for a single point symbol, returns the
861     * anchor point at which to display that image based off the image returned
862     * from the URL of the SinglePointServer.
863     *
864     * @param symbolID - the 15 character symbolID of a single point MilStd2525
865     * symbol.
866     * @return A pixel coordinate of the format "anchorX,anchorY,SymbolBoundsX,
867     * SymbolBoundsY,SymbolBoundsWidth,SymbolBoundsHeight,IconWidth,IconHeight".
868     * Anchor, represents the center point of the core symbol within the image.
869     * The image should be centered on this point.
870     * Symbol bounds represents the bounding rectangle of the core symbol within
871     * the image.
872     * IconWidth/Height represents the height and width of the image in its
873     * entirety.
874     * Returns an empty string if an error occurs.
875     */
876    public static String getSinglePointInfo(String symbolID)
877    {
878        String info = "";
879        Point2D anchor = new Point2D.Double();
880        Rectangle2D symbolBounds = new Rectangle2D.Double();
881        return info;
882    }
883        
884    /**
885     * Returns true if we recommend clipping a particular symbol.
886     * Would return false for and Ambush but would return true for a Line of 
887     * Contact due to the decoration on the line.
888     * @param symbolID
889     * @return 
890     */
891    public static String ShouldClipMultipointSymbol(String symbolID)
892    {
893        if(MultiPointHandler.ShouldClipSymbol(symbolID))
894            return "true";
895        else
896            return "false";
897    }
898    
899     /**
900     * Given a symbol code meant for a single point symbol, returns the
901     * symbol as a byte array.
902     *
903     * @param symbolID - the 15 character symbolID of a single point MilStd2525
904     * symbol.
905     * @return byte array.
906     */
907    public static byte[] getSinglePointByteArray(String symbolID)
908    {
909        //return sps.getSinglePointByteArray(symbolID);
910        return null;
911    }    
912     /**
913     * Put this here rather than in multipointhandler so that I could get the
914     * port info from the single point server.
915     * @param modifiers
916     * @param clip
917     * @return 
918     */
919    public static String GenerateSymbolLineFillUrl(SparseArray<String> modifiers, ArrayList<Point2D> pixels, Rectangle clip)
920    {
921        int shapeType = 0;
922        String url = "";
923        String symbolFillIDs=null;
924        String symbolLineIDs=null;
925        String strClip=null;
926        //int symbolSize = AreaSymbolFill.DEFAULT_SYMBOL_SIZE;
927        int symbolSize = 25;
928        int imageoffset = 0;
929        ArrayList<ArrayList<Point2D>> lines = null;
930        ArrayList<Point2D> points = null;
931        Point2D point = null;
932        
933        Shape shape = null;
934        //PathIterator itr = null;
935        double height = 0;
936        double width = 0;
937        int offsetX = 0;
938        int offsetY = 0;
939        int x = 0;
940        int y = 0;
941        //Rectangle2D bounds = null;
942        Rectangle bounds = null;
943        try
944        {
945            //Path2D path = new GeneralPath();
946            GeneralPath path = new GeneralPath();
947            Point2D temp = null;
948            //Get bounds of the polygon/polyline path
949            for(int i=0; i<pixels.size();i++)
950            {
951                temp = pixels.get(i);
952                if(i>0)
953                {
954                    path.lineTo(temp.getX(), temp.getY());
955                }
956                else if(i==0)
957                {
958                    path.moveTo(temp.getX(), temp.getY());
959                }
960            }
961            
962            bounds = path.getBounds();
963            height = bounds.getHeight();
964            width = bounds.getWidth();
965
966//            System.out.println("bounds: "+ bounds.toString());
967//                    System.out.println("height: "+ String.valueOf(height));
968//            System.out.println("width: "+ String.valueOf(width));
969            
970            //pixels may be in negative space so get offsets to put everything
971            //in the positive
972            if(bounds.getX()<0)
973            {
974                offsetX = (int)(bounds.getX()*-1);
975            }
976            else if((bounds.getX()+bounds.getWidth()) > width)
977            {
978                offsetX = (int)((bounds.getX()+bounds.getWidth())-width)*-1;
979            }
980            
981            if(bounds.getY()<0)
982            {
983                offsetY = (int)(bounds.getY()*-1);
984            }
985            else if((bounds.getY()+bounds.getHeight()) > height)
986            {
987                offsetY = (int)((bounds.getY()+bounds.getHeight())-height)*-1;
988            }
989
990            //build clip string
991            if(clip!=null)
992            {
993                StringBuilder sbClip = new StringBuilder();
994                sbClip.append("&clip=");
995                sbClip.append(Integer.toString((int)clip.getX()));
996                sbClip.append(",");
997                sbClip.append(Integer.toString((int)clip.getY()));
998                sbClip.append(",");
999                sbClip.append(Integer.toString((int)clip.getWidth()));
1000                sbClip.append(",");
1001                sbClip.append(Integer.toString((int)clip.getHeight()));
1002                strClip=sbClip.toString();
1003            }
1004
1005                    
1006            StringBuilder sbCoords = new StringBuilder();
1007            StringBuilder sbUrl = new StringBuilder();
1008            sbCoords.append("coords=");
1009
1010            if (modifiers.indexOfKey(ModifiersTG.SYMBOL_FILL_IDS) >= 0) {
1011                symbolFillIDs = (String) modifiers.get(ModifiersTG.SYMBOL_FILL_IDS);
1012            }
1013                        
1014            //build coordinate string
1015            for(int i = 0; i< pixels.size(); i++)
1016            {
1017                if(i>0)
1018                {
1019                    sbCoords.append(",");
1020                }
1021                point = pixels.get(i);
1022                x = (int)(point.getX() + offsetX);
1023                y = (int)(point.getY() + offsetY);
1024                sbCoords.append(Integer.toString(x));
1025                sbCoords.append(",");
1026                sbCoords.append(Integer.toString(y));
1027            }
1028            
1029            //build image url
1030            sbUrl.append("http://127.0.0.1:");
1031            //sbUrl.append(String.valueOf(spsPortNumber));
1032            sbUrl.append("6789");
1033            sbUrl.append("/AREASYMBOLFILL?");
1034            sbUrl.append("renderer=AreaSymbolFillRenderer&");
1035            sbUrl.append(sbCoords.toString());
1036            if(symbolFillIDs != null)
1037            {
1038                sbUrl.append("&symbolFillIds=");
1039                sbUrl.append(symbolFillIDs);
1040            }
1041            if(symbolLineIDs != null)
1042            {
1043                sbUrl.append("&symbolLineIds=");
1044                sbUrl.append(symbolLineIDs);
1045            }
1046            if(symbolSize>0)
1047            {
1048                sbUrl.append("&symbolFillIconSize=");                sbUrl.append(Integer.toString(symbolSize));
1049            }
1050            if(strClip!=null)
1051            {
1052                sbUrl.append(strClip);
1053            }
1054
1055
1056
1057            sbUrl.append("&height=");
1058            //sbUrl.append(Integer.valueOf((int)height));
1059            sbUrl.append(Integer.toString((int)height));
1060            sbUrl.append("&width=");
1061            //sbUrl.append(Integer.valueOf((int)width));
1062            sbUrl.append(Integer.toString((int)width));
1063
1064            url = sbUrl.toString();
1065
1066        }
1067        catch(Exception exc)
1068        {
1069            System.out.println(exc.getMessage());
1070            exc.printStackTrace();
1071        }
1072        return url;
1073    }
1074
1075}