001package armyc2.c5isr.web.render;
002
003import android.graphics.Bitmap;
004
005import armyc2.c5isr.graphics2d.BasicStroke;
006import armyc2.c5isr.graphics2d.Point2D;
007import armyc2.c5isr.graphics2d.Rectangle;
008import armyc2.c5isr.renderer.utilities.*;
009import armyc2.c5isr.web.render.utilities.Path;
010import armyc2.c5isr.web.render.utilities.SVGTextInfo;
011
012import java.util.ArrayList;
013
014public class MultiPointHandlerSVG {
015    public static String GeoSVGize(String id, String name, String description, String symbolID, ArrayList<ShapeInfo> shapes, ArrayList<ShapeInfo> modifiers, IPointConversion ipc, boolean normalize, String textColor, String textBackgroundColor, boolean wasClipped) {
016        return GeoSVGize(id, name, description, symbolID, shapes, modifiers, ipc, normalize, textColor, textBackgroundColor, wasClipped, null);
017    }
018
019    /**
020     * Generates an SVG which can be draped on a map.
021     * Better with RenderSymbol2D
022     *
023     * @param id
024     * @param name
025     * @param description
026     * @param symbolID
027     * @param shapes              {@link ShapeInfo[]}
028     * @param modifiers           {@link ShapeInfo[]}
029     * @param ipc                 {@link IPointConversion}
030     * @param normalize
031     * @param textColor
032     * @param textBackgroundColor
033     * @param wasClipped
034     * @return
035     */
036    public static String GeoSVGize(String id, String name, String description, String symbolID, ArrayList<ShapeInfo> shapes, ArrayList<ShapeInfo> modifiers, IPointConversion ipc, boolean normalize, String textColor, String textBackgroundColor, boolean wasClipped, Rectangle bbox) {
037
038        int height = 10;
039
040        Rectangle tempBounds = null;
041        ArrayList<String> paths = new ArrayList<>();
042        Rectangle pathBounds = null;
043        ArrayList<SVGTextInfo> labels = new ArrayList<>();
044        Rectangle labelBounds = null;
045        Rectangle unionBounds = null;
046        float lineWidth;
047        String fillTexture = null;
048        Point2D geoCoordTL = null;
049        Point2D geoCoordTR = null;
050        Point2D geoCoordBL = null;
051        Point2D geoCoordBR = null;
052        Point2D west = null;
053        Point2D north = null;
054        Point2D south = null;
055        Point2D east = null;
056        int len = shapes.size();
057
058        try {
059            height = RendererSettings.getInstance().getMPLabelFontSize();
060
061            for (int i = 0; i < len; i++) {
062                tempBounds = new Rectangle();
063                String svg = MultiPointHandlerSVG.ShapesToGeoSVG(symbolID, shapes.get(i), tempBounds, ipc, normalize);
064                if (svg != null) {
065                    lineWidth = shapes.get(i).getStroke().getLineWidth();
066                    tempBounds.grow(Math.round(lineWidth / 2), Math.round(lineWidth / 2));//adjust for line width so nothing gets clipped.
067                    if (pathBounds == null)
068                        pathBounds = new Rectangle(tempBounds);
069                    else
070                        pathBounds = pathBounds.union(tempBounds);
071                    paths.add(svg);
072
073                    if (shapes.get(i).getPatternFillImage() != null && fillTexture == null)
074                        fillTexture = getFillPattern(shapes.get(i));
075                }
076            }
077
078            ShapeInfo tempModifier;
079            int len2 = modifiers.size();
080            SVGTextInfo tiTemp = null;
081            for (int j = 0; j < len2; j++) {
082                tempModifier = modifiers.get(j);
083
084                if (tempModifier.getModifierString() != null && !tempModifier.getModifierString().isEmpty()) {
085                    Point2D tempLocation = tempModifier.getModifierPosition();
086
087                    int justify = tempModifier.getTextJustify();
088                    String strJustify = "start";
089                    if (justify == ShapeInfo.justify_left)
090                        strJustify = "start";
091                    else if (justify == ShapeInfo.justify_center)
092                        strJustify = "middle";
093                    else if (justify == ShapeInfo.justify_right)
094                        strJustify = "end";
095
096                    double degrees = tempModifier.getModifierAngle();
097                    tiTemp = new SVGTextInfo(tempModifier.getModifierString(), tempLocation, strJustify, degrees);
098
099                    Rectangle bounds = tiTemp.getTextBounds().getBounds();
100
101                    //make sure labels are in the bbox, otherwise they can
102                    //make the canvas grow out of control.
103                    //if (tiTemp && bbox.containsRectangle(bounds))
104                    //if(bbox !== null)
105                    if (tiTemp != null) {
106                        if ((bbox != null && bbox.intersects(bounds)) || bbox == null) {
107                            labels.add(tiTemp);
108                            if (bounds != null) {
109                                if (labelBounds != null)
110                                    labelBounds = labelBounds.union(bounds);
111                                else
112                                    labelBounds = bounds;
113                            }
114                        }
115                    }
116                } else if (tempModifier.getModifierImage() != null) {
117                    Bitmap imgModifier = tempModifier.getModifierImage();
118                    Rectangle bounds = new Rectangle(0, 0, imgModifier.getWidth(), imgModifier.getHeight());
119
120                    Point2D tempLocation = tempModifier.getModifierPosition();
121                    tempLocation.setLocation(tempLocation.getX() - bounds.getWidth() / 2, tempLocation.getY() - bounds.getHeight() / 2);
122                    int x = (int) tempLocation.getX();
123                    int y = (int) tempLocation.getY();
124                    bounds.setLocation(x, y);
125
126                    double angle = tempModifier.getModifierAngle();
127                    paths.add("<image transform=\"translate(" + x + ',' + y + ") rotate(" + angle + ")\" href=\"" + MultiPointHandler.bitmapToString(tempModifier.getModifierImage()) + "\" />");
128                    if (angle != 0) {
129                        bounds = SVGTextInfo.getRotatedRectangleBounds(bounds, tempLocation, -angle, "middle");
130                    }
131                    if (bounds != null) {
132                        if ((bbox != null && bbox.intersects(bounds)) || bbox == null) {
133                            if (pathBounds != null)
134                                pathBounds = pathBounds.union(bounds);
135                            else
136                                pathBounds = bounds;
137                        }
138                    }
139                }
140            }
141            if (pathBounds != null) {
142                unionBounds = new Rectangle(pathBounds);
143            }
144            if (labelBounds != null) {
145                if (unionBounds != null) {
146                    unionBounds = unionBounds.union(labelBounds);
147                } else {
148                    unionBounds = labelBounds;
149                }
150            }
151
152            //get geo bounds for canvas
153
154            if (unionBounds != null) {
155                Point2D coordTL = new Point2D.Double();
156                coordTL.setLocation(unionBounds.getX(), unionBounds.getY());
157                Point2D coordBR = new Point2D.Double();
158                coordBR.setLocation(unionBounds.getX() + unionBounds.getWidth(), unionBounds.getY() + unionBounds.getHeight());
159
160                Point2D coordTR = new Point2D.Double();
161                coordTR.setLocation(unionBounds.getX() + unionBounds.getWidth(), unionBounds.getY());
162                Point2D coordBL = new Point2D.Double();
163                coordBL.setLocation(unionBounds.getX(), unionBounds.getY() + unionBounds.getHeight());
164
165                south = new Point2D.Double(unionBounds.getX() + unionBounds.getWidth() / 2, unionBounds.getY() + unionBounds.getHeight());
166                north = new Point2D.Double(unionBounds.getX() + unionBounds.getWidth() / 2, unionBounds.getY());
167                east = new Point2D.Double(unionBounds.getX() + unionBounds.getWidth(), unionBounds.getY() + unionBounds.getHeight() / 2);
168                west = new Point2D.Double(unionBounds.getX(), unionBounds.getY() + unionBounds.getHeight() / 2);
169
170
171                geoCoordTL = ipc.PixelsToGeo(coordTL);
172                geoCoordBR = ipc.PixelsToGeo(coordBR);
173                geoCoordTR = ipc.PixelsToGeo(coordTR);
174                geoCoordBL = ipc.PixelsToGeo(coordBL);
175
176                north = ipc.PixelsToGeo(north);
177                south = ipc.PixelsToGeo(south);
178                east = ipc.PixelsToGeo(east);
179                west = ipc.PixelsToGeo(west);
180
181
182                if (normalize) {
183                    geoCoordTL = MultiPointHandler.NormalizeCoordToGECoord(geoCoordTL);
184                    geoCoordBR = MultiPointHandler.NormalizeCoordToGECoord(geoCoordBR);
185                    geoCoordTR = MultiPointHandler.NormalizeCoordToGECoord(geoCoordTR);
186                    geoCoordBL = MultiPointHandler.NormalizeCoordToGECoord(geoCoordBL);
187
188                    north = MultiPointHandler.NormalizeCoordToGECoord(north);
189                    south = MultiPointHandler.NormalizeCoordToGECoord(south);
190                    east = MultiPointHandler.NormalizeCoordToGECoord(east);
191                    west = MultiPointHandler.NormalizeCoordToGECoord(west);
192                }
193            } else//nothing to draw
194            {
195                geoCoordTL = new Point2D.Double(0, 0);
196                geoCoordBR = new Point2D.Double(0, 0);
197                geoCoordTR = new Point2D.Double(0, 0);
198                geoCoordBL = new Point2D.Double(0, 0);
199
200                north = new Point2D.Double(0, 0);
201                south = new Point2D.Double(0, 0);
202                east = new Point2D.Double(0, 0);
203                west = new Point2D.Double(0, 0);
204            }
205        } catch (Exception err) {
206            ErrorLogger.LogException("MultiPointHandler", "GeoSVGize", err);
207        }
208
209        if (paths != null && len > 0 && unionBounds != null) {
210            //create group with offset translation
211            //ctx.translate(bounds.getX() * -1, bounds.getY() * -1);
212            String group = "<g transform=\"translate(" + (unionBounds.getX() * -1) + ',' + (unionBounds.getY() * -1) + ")\">";
213
214            //loop through paths and labels and build SVG.
215            for (int i = 0; i < paths.size(); i++) {
216                group += paths.get(i);
217            }
218
219            ArrayList<String> labelStrs = renderTextElement(labels, textColor, textBackgroundColor);
220            for (int j = 0; j < labelStrs.size(); j++) {
221                group += labelStrs.get(j);
222            }
223            //close
224            group += "</g>";
225
226            //wrap in SVG
227            String geoSVG = "<svg width=\"" + Math.ceil(unionBounds.getWidth()) + "px\" height=\"" + Math.ceil(unionBounds.getHeight()) + "px\" preserveAspectRatio=\"none\" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">";
228
229            geoSVG += ("<metadata>\n");
230            geoSVG += ("<id>") + id + ("</id>\n");
231            geoSVG += ("<name>") + name + ("</name>\n");
232            geoSVG += ("<description>") + description + ("</description>\n");
233            geoSVG += ("<symbolID>") + symbolID + ("</symbolID>\n");
234            geoSVG += ("<geoTL>") + geoCoordTL.getX() + " " + geoCoordTL.getY() + ("</geoTL>\n");
235            geoSVG += ("<geoBR>") + geoCoordBR.getX() + " " + geoCoordBR.getY() + ("</geoBR>\n");
236            geoSVG += ("<geoTR>") + geoCoordTR.getX() + " " + geoCoordTR.getY() + ("</geoTR>\n");
237            geoSVG += ("<geoBL>") + geoCoordBL.getX() + " " + geoCoordBL.getY() + ("</geoBL>\n");
238            geoSVG += ("<north>") + north.getY() + ("</north>\n");
239            geoSVG += ("<south>") + south.getY() + ("</south>\n");
240            geoSVG += ("<east>") + east.getX() + ("</east>\n");
241            geoSVG += ("<west>") + west.getX() + ("</west>\n");
242            geoSVG += ("<wasClipped>") + wasClipped + ("</wasClipped>\n");
243            geoSVG += ("<width>") + unionBounds.getWidth() + ("</width>\n");
244            geoSVG += ("<height>") + unionBounds.getHeight() + ("</height>\n");
245            geoSVG += ("</metadata>\n");
246
247
248            /*//Scale the image, commented out as I decided to alter scale in getReasonableScale rather than adjust after the fact.
249            var tempWidth = Math.ceil(unionBounds.getWidth());
250            var tempHeight = Math.ceil(unionBounds.getHeight());
251            var quality = 1.0;
252            var bigger = Math.max(tempWidth, tempHeight);
253            var max = 1000;
254            if(!converter)
255            {
256                if(bigger < max)
257                {
258                    if(bigger * 2 < max)
259                    {
260                        quality = 2;
261                    }
262                    else
263                    {
264                        quality = max / bigger;
265                    }
266                }
267                else
268                {
269                    quality = 1;
270                }
271            }
272            var geoSVG = '<svg viewBox="0 0 ' + tempWidth + ' ' + tempHeight + '"' + ' width="' + (tempWidth * quality) + 'px" height="' + (tempHeight * quality) + 'px" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg" version="1.1">';//*/
273            if (fillTexture != null)
274                geoSVG += fillTexture;
275            geoSVG += group;
276            geoSVG += "</svg>";//*/
277
278            return geoSVG;
279
280        } else {
281            //return blank 2x2 SVG
282            return "<svg width=\"2px\" height=\"2px\" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\"></svg>";
283        }
284    }
285
286    /**
287     * @param tiArray      {@link SVGTextInfo[]}
288     * @param color        a hex string "#000000"
289     * @param outlineColor a hex string "#000000"
290     */
291    static ArrayList<String> renderTextElement(ArrayList<SVGTextInfo> tiArray, String color, String outlineColor) {
292        //ctx.lineCap = "butt";
293        //ctx.lineJoin = "miter";
294        //ctx.miterLimit = 3;
295        /*ctx.lineCap = "round";
296        ctx.lineJoin = "round";
297        ctx.miterLimit = 3;*/
298        ArrayList<String> svgElements = new ArrayList<>();
299
300        int size = tiArray.size();
301        SVGTextInfo tempShape = null;
302        String textColor = "#000000";
303        int tbm = RendererSettings.getInstance().getTextBackgroundMethod();
304        int outlineWidth = RendererSettings.getInstance().getTextOutlineWidth();
305
306        if (color != null) {
307            textColor = color;
308        }
309
310
311        if (outlineColor == null || outlineColor.isEmpty()) {
312            outlineColor = RendererUtilities.colorToHexString(RendererUtilities.getIdealOutlineColor(RendererUtilities.getColorFromHexString(textColor)), false);
313        }
314
315
316        if (tbm == RendererSettings.TextBackgroundMethod_OUTLINE
317                || tbm == RendererSettings.TextBackgroundMethod_OUTLINE_QUICK
318                || tbm == RendererSettings.TextBackgroundMethod_COLORFILL) {
319            for (int i = 0; i < size; i++) {
320                tempShape = tiArray.get(i);
321                svgElements.add(tempShape.toSVGElement(textColor, outlineColor, outlineWidth));
322            }
323        } else //if(tbm == RendererSettings.TextBackgroundMethod_NONE)
324        {
325            for (int j = 0; j < size; j++) {
326                tempShape = tiArray.get(j);
327                svgElements.add(tempShape.toSVGElement(textColor, null, 0));
328            }
329        }
330
331        return svgElements;
332    }
333
334    static String getFillPattern(ShapeInfo shapeInfo) {
335        if (shapeInfo.getPatternFillImage() != null) {
336            int width = shapeInfo.getPatternFillImage().getWidth();
337            int height = shapeInfo.getPatternFillImage().getHeight();
338            return "<defs><pattern id=\"fillPattern\" patternUnits=\"userSpaceOnUse\" width=\"" + width + "\" height=\"" + height + "\"><image href=\"" + MultiPointHandler.bitmapToString(shapeInfo.getPatternFillImage()) + "\" /></pattern></defs>";
339        } else {
340            return null;
341        }
342    }
343
344    /**
345     * @param symbolID
346     * @param shapeInfo  {@link ShapeInfo}
347     * @param pathBounds {@link Rectangle}
348     * @param ipc        {@link IPointConversion}
349     * @param normalize
350     */
351    static String ShapesToGeoSVG(String symbolID, ShapeInfo shapeInfo, Rectangle pathBounds, IPointConversion ipc, boolean normalize) {
352        Path path = null;
353        String fillColor = null;
354        String lineColor = null;
355        int lineWidth = 0;
356        double lineAlpha = 1.0;
357        double fillAlpha = 1.0;
358        float[] dashArray = null;
359        String fillPattern = null;
360
361        if (shapeInfo.getLineColor() != null) {
362            Color lineColorTemp = shapeInfo.getLineColor();
363            lineAlpha = lineColorTemp.getAlpha() / 255.0;
364            lineColor = RendererUtilities.colorToHexString(lineColorTemp, false);
365        }
366        if (shapeInfo.getFillColor() != null) {
367            Color fillColorTemp = shapeInfo.getFillColor();
368            fillAlpha = fillColorTemp.getAlpha() / 255.0;
369            fillColor = RendererUtilities.colorToHexString(fillColorTemp, false);
370        }
371
372        BasicStroke stroke = shapeInfo.getStroke();
373        if (stroke != null) {
374            lineWidth = Math.round(stroke.getLineWidth());
375            dashArray = stroke.getDashArray();
376        }
377
378        ArrayList<ArrayList<Point2D>> shapesArray = shapeInfo.getPolylines();
379        path = new Path();
380        if (dashArray != null && dashArray.length > 0)
381            path.setLineDash(dashArray);
382        for (int i = 0; i < shapesArray.size(); i++) {
383            ArrayList<Point2D> shape = shapesArray.get(i);
384
385            for (int j = 0; j < shape.size(); j++) {
386                Point2D coord = shape.get(j);
387                if (j == 0) {
388                    path.moveTo(coord.getX(), coord.getY());
389                } else if (dashArray != null) {
390                    path.dashedLineTo(coord.getX(), coord.getY());
391                } else {
392                    path.lineTo(coord.getX(), coord.getY());
393                }
394            }
395        }
396        if (shapeInfo.getPatternFillImage() != null)
397            fillColor = "url(#fillPattern)";
398        String svgElement = path.toSVGElement(lineColor, lineWidth, fillColor, lineAlpha, fillAlpha);
399        pathBounds.setRect(new Rectangle(path.getBounds()));
400        return svgElement;
401    }
402}