001package armyc2.c5isr.renderer.utilities; 002 003import android.graphics.Canvas; 004import android.graphics.Paint; 005import android.graphics.Paint.Style; 006import android.graphics.Point; 007import android.graphics.Rect; 008import android.graphics.RectF; 009import android.util.SparseArray; 010 011import java.util.Map; 012import java.util.TreeSet; 013import java.util.logging.Level; 014import java.util.regex.Matcher; 015import java.util.regex.Pattern; 016 017public class RendererUtilities { 018 019 private static final float OUTLINE_SCALING_FACTOR = 2.5f; 020 private static SparseArray<Color> pastIdealOutlineColors = new SparseArray<Color>(); 021 /** 022 * 023 * @param color {String} color like "#FFFFFF" 024 * @return {String} 025 */ 026 public static Color getIdealOutlineColor(Color color){ 027 Color idealColor = Color.white; 028 029 if(color != null && pastIdealOutlineColors.indexOfKey(color.toInt())>=0) 030 { 031 return pastIdealOutlineColors.get(color.toInt()); 032 }//*/ 033 034 if(color != null) 035 { 036 037 int threshold = RendererSettings.getInstance().getTextBackgroundAutoColorThreshold(); 038 039 int r = color.getRed(); 040 int g = color.getGreen(); 041 int b = color.getBlue(); 042 043 float delta = ((r * 0.299f) + (g * 0.587f) + (b * 0.114f)); 044 045 if((255 - delta < threshold)) 046 { 047 idealColor = Color.black; 048 } 049 else 050 { 051 idealColor = Color.white; 052 } 053 } 054 055 if(color != null) 056 pastIdealOutlineColors.put(color.toInt(),idealColor); 057 058 return idealColor; 059 } 060 061 public static void renderSymbolCharacter(Canvas ctx, String symbol, int x, int y, Paint paint, Color color, int outlineWidth) 062 { 063 int tbm = RendererSettings.getInstance().getTextBackgroundMethod(); 064 065 Color outlineColor = RendererUtilities.getIdealOutlineColor(color); 066 067 //if(tbm == RendererSettings.TextBackgroundMethod_OUTLINE_QUICK) 068 //{ 069 //draw symbol outline 070 paint.setStyle(Style.FILL); 071 072 paint.setColor(outlineColor.toInt()); 073 if(outlineWidth > 0) 074 { 075 for(int i = 1; i <= outlineWidth; i++) 076 { 077 if(i % 2 == 1) 078 { 079 ctx.drawText(symbol, x - i, y, paint); 080 ctx.drawText(symbol, x + i, y, paint); 081 ctx.drawText(symbol, x, y + i, paint); 082 ctx.drawText(symbol, x, y - i, paint); 083 } 084 else 085 { 086 ctx.drawText(symbol, x - i, y - i, paint); 087 ctx.drawText(symbol, x + i, y - i, paint); 088 ctx.drawText(symbol, x - i, y + i, paint); 089 ctx.drawText(symbol, x + i, y + i, paint); 090 } 091 092 } 093 094 } 095 //draw symbol 096 paint.setColor(color.toInt()); 097 098 ctx.drawText(symbol, x, y, paint); 099 100 /*} 101 else 102 { 103 //draw text outline 104 paint.setStyle(Style.STROKE); 105 paint.setStrokeWidth(RendererSettings.getInstance().getTextOutlineWidth()); 106 paint.setColor(outlineColor.toInt()); 107 if(outlineWidth > 0) 108 { 109 110 ctx.drawText(symbol, x, y, paint); 111 112 } 113 //draw text 114 paint.setColor(color.toInt()); 115 paint.setStyle(Style.FILL); 116 117 ctx.drawText(symbol, x, y, paint); 118 }//*/ 119 } 120 121 /** 122 * Create a copy of the {@Color} object with the passed alpha value. 123 * @param color {@Color} object used for RGB values 124 * @param alpha {@float} value between 0 and 1 125 * @return 126 */ 127 public static Color setColorAlpha(Color color, float alpha) { 128 if (color != null) 129 { 130 if(alpha >= 0 && alpha <= 1) 131 return new Color(color.getRed(),color.getGreen(),color.getBlue(),(int)(alpha*255f)); 132 else 133 return color; 134 } 135 else 136 return null; 137 } 138 public static String colorToHexString(Color color, Boolean withAlpha) 139 { 140 if(color != null) 141 { 142 String hex = color.toHexString(); 143 hex = hex.toUpperCase(); 144 if(withAlpha) 145 return "#" + hex; 146 else 147 return "#" + hex.substring(2); 148 } 149 return null; 150 } 151 152 /** 153 * 154 * @param hexValue - String representing hex value (formatted "0xRRGGBB" 155 * i.e. "0xFFFFFF") OR formatted "0xAARRGGBB" i.e. "0x00FFFFFF" for a color 156 * with an alpha value I will also put up with "RRGGBB" and "AARRGGBB" 157 * without the starting "0x" 158 * @return 159 */ 160 public static Color getColorFromHexString(String hexValue) 161 { 162 try 163 { 164 if(hexValue==null || hexValue.isEmpty()) 165 return null; 166 String hexOriginal = hexValue; 167 168 String hexAlphabet = "0123456789ABCDEF"; 169 170 if (hexValue.charAt(0) == '#') 171 { 172 hexValue = hexValue.substring(1); 173 } 174 if (hexValue.substring(0, 2).equals("0x") || hexValue.substring(0, 2).equals("0X")) 175 { 176 hexValue = hexValue.substring(2); 177 } 178 179 hexValue = hexValue.toUpperCase(); 180 181 int count = hexValue.length(); 182 int[] value = null; 183 int k = 0; 184 int int1 = 0; 185 int int2 = 0; 186 187 if (count == 8 || count == 6) 188 { 189 value = new int[(count / 2)]; 190 for (int i = 0; i < count; i += 2) 191 { 192 int1 = hexAlphabet.indexOf(hexValue.charAt(i)); 193 int2 = hexAlphabet.indexOf(hexValue.charAt(i + 1)); 194 195 if(int1 == -1 || int2 == -1) 196 { 197 ErrorLogger.LogMessage("RendererUtilities", "getColorFromHexString", "Bad hex value: " + hexOriginal, Level.WARNING); 198 return null; 199 } 200 201 value[k] = (int1 * 16) + int2; 202 k++; 203 } 204 205 if (count == 8) 206 { 207 return new Color(value[1], value[2], value[3], value[0]); 208 } 209 else if (count == 6) 210 { 211 return new Color(value[0], value[1], value[2]); 212 } 213 } 214 else 215 { 216 ErrorLogger.LogMessage("RendererUtilities", "getColorFromHexString", "Bad hex value: " + hexOriginal, Level.WARNING); 217 } 218 return null; 219 } 220 catch (Exception exc) 221 { 222 ErrorLogger.LogException("RendererUtilities", "getColorFromHexString", exc); 223 return null; 224 } 225 } 226 227 /** 228 * For Renderer Use Only 229 * Assumes a fresh SVG String from the SVGLookup with its default values 230 * @param symbolID 231 * @param svg 232 * @param strokeColor hex value like "#FF0000"; 233 * @param fillColor hex value like "#FF0000"; 234 * @return SVG String 235 */ 236 public static String setSVGFrameColors(String symbolID, String svg, Color strokeColor, Color fillColor) 237 { 238 String returnSVG = null; 239 String hexStrokeColor = null; 240 String hexFillColor = null; 241 float strokeAlpha = 1; 242 float fillAlpha = 1; 243 String strokeOpacity = ""; 244 String fillOpacity = ""; 245 246 int ss = SymbolID.getSymbolSet(symbolID); 247 int ver = SymbolID.getVersion(symbolID); 248 int affiliation = SymbolID.getAffiliation(symbolID); 249 String defaultFillColor = null; 250 returnSVG = svg; 251 if(strokeColor != null) 252 { 253 if(strokeColor.getAlpha() != 255) 254 { 255 strokeAlpha = strokeColor.getAlpha() / 255.0f; 256 strokeOpacity = " stroke-opacity=\"" + String.valueOf(strokeAlpha) + "\""; 257 fillOpacity = " fill-opacity=\"" + String.valueOf(strokeAlpha) + "\""; 258 } 259 260 hexStrokeColor = colorToHexString(strokeColor,false); 261 returnSVG = svg.replaceAll("stroke=\"#000000\"", "stroke=\"" + hexStrokeColor + "\"" + strokeOpacity); 262 returnSVG = returnSVG.replaceAll("fill=\"#000000\"", "fill=\"" + hexStrokeColor + "\"" + fillOpacity); 263 264 if(ss == SymbolID.SymbolSet_LandInstallation || 265 ss == SymbolID.SymbolSet_Space || 266 ss == SymbolID.SymbolSet_CyberSpace || 267 ss == SymbolID.SymbolSet_Activities) 268 {//add group fill so the extra shapes in these frames have the new frame color 269 String svgStart = "<g id=\"" + SVGLookup.getFrameID(symbolID) + "\">"; 270 String svgStartReplace = svgStart.substring(0,svgStart.length()-1) + " fill=\"" + hexStrokeColor + "\"" + fillOpacity + ">"; 271 returnSVG = returnSVG.replace(svgStart,svgStartReplace); 272 } 273 274 if((SymbolID.getSymbolSet(symbolID)==SymbolID.SymbolSet_LandInstallation && SymbolID.getFrameShape(symbolID)=='0') || 275 SymbolID.getFrameShape(symbolID)==SymbolID.FrameShape_LandInstallation) 276 { 277 int i1 = findInstIndIndex(returnSVG)+5; 278 //make sure installation indicator matches line color 279 returnSVG = returnSVG.substring(0,i1) + " fill=\"" + hexStrokeColor + "\"" + returnSVG.substring(i1); 280 } 281 282 } 283 else if((SymbolID.getSymbolSet(symbolID)==SymbolID.SymbolSet_LandInstallation && SymbolID.getFrameShape(symbolID)=='0') || 284 SymbolID.getFrameShape(symbolID)==SymbolID.FrameShape_LandInstallation) 285 { 286 int i1 = findInstIndIndex(returnSVG)+5; 287 //No line color change so make sure installation indicator stays black 288 returnSVG = returnSVG.substring(0,i1) + " fill=\"#000000\"" + returnSVG.substring(i1); 289 } 290 291 if(fillColor != null) 292 { 293 if(fillColor.getAlpha() != 255) 294 { 295 fillAlpha = fillColor.getAlpha() / 255.0f; 296 fillOpacity = " fill-opacity=\"" + String.valueOf(fillAlpha) + "\""; 297 } 298 299 hexFillColor = colorToHexString(fillColor,false); 300 switch(affiliation) 301 { 302 case SymbolID.StandardIdentity_Affiliation_Friend: 303 case SymbolID.StandardIdentity_Affiliation_AssumedFriend: 304 defaultFillColor = "fill=\"#80E0FF\"";//friendly frame fill 305 break; 306 case SymbolID.StandardIdentity_Affiliation_Hostile_Faker: 307 defaultFillColor = "fill=\"#FF8080\"";//hostile frame fill 308 break; 309 case SymbolID.StandardIdentity_Affiliation_Suspect_Joker: 310 if(SymbolID.getVersion(symbolID) >= SymbolID.Version_2525E) 311 defaultFillColor = "fill=\"#FFE599\"";//suspect frame fill 312 else 313 defaultFillColor = "fill=\"#FF8080\"";//hostile frame fill 314 break; 315 case SymbolID.StandardIdentity_Affiliation_Unknown: 316 case SymbolID.StandardIdentity_Affiliation_Pending: 317 defaultFillColor = "fill=\"#FFFF80\"";//unknown frame fill 318 break; 319 case SymbolID.StandardIdentity_Affiliation_Neutral: 320 defaultFillColor = "fill=\"#AAFFAA\"";//neutral frame fill 321 break; 322 default: 323 defaultFillColor = "fill=\"#80E0FF\"";//friendly frame fill 324 break; 325 } 326 327 int fillIndex = returnSVG.lastIndexOf(defaultFillColor); 328 if(fillIndex != -1) 329 returnSVG = returnSVG.substring(0,fillIndex) + "fill=\"" + hexFillColor + "\"" + fillOpacity + returnSVG.substring(fillIndex + defaultFillColor.length()); 330 331 //returnSVG = returnSVG.replaceFirst(defaultFillColor, "fill=\"" + hexFillColor + "\"" + fillOpacity); 332 } 333 334 if(returnSVG != null) 335 return returnSVG; 336 else 337 return svg; 338 } 339 340 /** 341 * For Renderer Use Only 342 * Changes colors for single point control measures 343 * @param symbolID 344 * @param svg 345 * @param strokeColor hex value like "#FF0000"; 346 * @param fillColor hex value like "#FF0000"; 347 * @param isOutline true if this represents a thicker outline to render first beneath the normal symbol (the function must be called twice) 348 * @return SVG String 349 */ 350 public static String setSVGSPCMColors(String symbolID, String svg, Color strokeColor, Color fillColor, boolean isOutline, RectF bounds, int pixelSize, int outlineWidth) 351 { 352 String returnSVG = svg; 353 String hexStrokeColor = null; 354 String hexFillColor = null; 355 float strokeAlpha = 1; 356 float fillAlpha = 1; 357 String strokeOpacity = ""; 358 String fillOpacity = ""; 359 String strokeCapSquare = " stroke-linecap=\"square\""; 360 String strokeCapButt = " stroke-linecap=\"butt\""; 361 String strokeCapRound = " stroke-linecap=\"round\""; 362 int outlineSize = 15; 363 364 int affiliation = SymbolID.getAffiliation(symbolID); 365 String defaultFillColor = null; 366 if(strokeColor != null) 367 { 368 if(strokeColor.getAlpha() != 255) 369 { 370 strokeAlpha = strokeColor.getAlpha() / 255.0f; 371 strokeOpacity = " stroke-opacity=\"" + strokeAlpha + "\""; 372 fillOpacity = " fill-opacity=\"" + strokeAlpha + "\""; 373 } 374 375 hexStrokeColor = colorToHexString(strokeColor,false); 376 String defaultStrokeColor = "#000000"; 377 if(symbolID.length()==5) 378 { 379 int mod = Integer.valueOf(symbolID.substring(2,4)); 380 if(mod >= 13) 381 defaultStrokeColor = "#00A651"; 382 383 } 384 385 if(symbolID.length() >= 20) 386 { 387 if(SymbolUtilities.getBasicSymbolID(symbolID).equals("25132100") && //key terrain 388 SymbolID.getVersion(symbolID) >= SymbolID.Version_2525E) 389 defaultStrokeColor = "#800080"; 390 else if(isOutline && SymbolUtilities.getBasicSymbolID(symbolID).startsWith("2535"))//space debris doesn't change color 391 defaultStrokeColor = "black"; 392 } 393 returnSVG = returnSVG.replaceAll("stroke=\"" + defaultStrokeColor + "\"", "stroke=\"" + hexStrokeColor + "\"" + strokeOpacity); 394 returnSVG = returnSVG.replaceAll("fill=\"" + defaultStrokeColor + "\"", "fill=\"" + hexStrokeColor + "\"" + fillOpacity); 395 } 396 else 397 { 398 strokeColor = Color.BLACK; 399 } 400 401 if (isOutline && bounds != null) 402 { 403 float p = pixelSize; 404 double h = bounds.height(); 405 double w = bounds.width(); 406 double ratio = Math.min((p / h), (p / w)); 407 408 outlineSize = (int)Math.round(outlineWidth / ratio); 409 //increase stroke-width so the white outline shows around the symbol 410 returnSVG = increaseStrokeWidth(returnSVG,outlineSize); 411 //set the stroke color for the group so filled shapes without stokes get outlined as well. 412 returnSVG = returnSVG.replaceFirst("<g", "<g stroke=\"" + hexStrokeColor + "\" " + strokeOpacity + " stroke-linecap=\"square\""); 413 414 } 415 else 416 { 417 String replacement = " fill=\"" + colorToHexString(strokeColor,false) + "\" "; 418 returnSVG = returnSVG.replace("fill=\"#000000\"",replacement);//only replace black fills, leave white fills alone. 419 420 //In case there are lines that don't have stroke defined, apply stroke color to the top level group. 421 String topGroupTag = "<g id=\"" + SymbolUtilities.getBasicSymbolID(symbolID) + "\">";//<g id="25212902"> 422 String newGroupTag = "<g id=\"" + SymbolUtilities.getBasicSymbolID(symbolID) + "\" stroke=\"" + hexStrokeColor + "\"" + strokeOpacity + " " + replacement + ">"; 423 returnSVG = returnSVG.replace(topGroupTag,newGroupTag); 424 } 425 426 if(fillColor != null) 427 { 428 if(fillColor.getAlpha() != 255) 429 { 430 fillAlpha = fillColor.getAlpha() / 255.0f; 431 fillOpacity = " fill-opacity=\"" + fillAlpha + "\""; 432 } 433 434 hexFillColor = colorToHexString(fillColor,false); 435 defaultFillColor = "fill=\"#000000\""; 436 437 returnSVG = returnSVG.replaceAll(defaultFillColor, "fill=\"" + hexFillColor + "\"" + fillOpacity); 438 } 439 440 return returnSVG; 441 } 442 443 /** 444 * Sets SVG stroke-dasharray when action points are in planned status 445 * @param symbolID 446 * @param siIcon 447 * @return 448 */ 449 public static SVGInfo setAffiliationDashArray(String symbolID, SVGInfo siIcon) 450 { 451 String svg = siIcon.getSVG(); 452 int status = SymbolID.getStatus(symbolID); 453 int aff = SymbolID.getAffiliation(symbolID); 454 SVGInfo returnVal = siIcon; 455 if(status == SymbolID.Status_Planned_Anticipated_Suspect) 456 { 457 if(SymbolUtilities.isActionPoint(symbolID)) 458 { 459 svg = svg.replaceFirst("<rect ","<rect stroke-dasharray=\"20 19\" "); 460 svg = svg.replaceFirst("<polygon ","<polygon stroke-dasharray=\"20 20\" "); 461 returnVal = new SVGInfo(siIcon.getID(),siIcon.getBbox(), svg); 462 } 463 } 464 /*else if(aff == SymbolID.StandardIdentity_Affiliation_Pending || 465 aff == SymbolID.StandardIdentity_Affiliation_AssumedFriend || 466 aff == SymbolID.StandardIdentity_Affiliation_Suspect_Joker) 467 { 468 //Dot pattern if Control Measures use it? 469 }//*/ 470 471 return returnVal; 472 } 473 public static float findWidestStrokeWidth(String svg) { 474 Pattern pattern = Pattern.compile("(stroke-width=\")(\\d+\\.?\\d*)\""); 475 Matcher m = pattern.matcher(svg); 476 TreeSet<Float> strokeWidths = new TreeSet<>(); 477 while (m.find()) { 478 // Log.d("found stroke width", m.group(0)); 479 strokeWidths.add(Float.valueOf(m.group(2))); 480 } 481 482 float largest = 4.0f; 483 if (!strokeWidths.isEmpty()) { 484 largest = strokeWidths.descendingSet().first(); 485 } 486 return largest * OUTLINE_SCALING_FACTOR; 487 } 488 489 public static int findInstIndIndex(String svg) 490 { 491 int start = -1; 492 int stop = -1; 493 494 start = svg.indexOf("<rect"); 495 stop = svg.indexOf(">",start); 496 497 String rect = svg.substring(start,stop+1); 498 if(!rect.contains("fill"))//no set fill so it's the indicator 499 { 500 return start; 501 } 502 else //it's the next rect 503 { 504 start = svg.indexOf("<rect",stop); 505 } 506 507 return start; 508 } 509 510 public static SVGInfo scaleIcon(String symbolID, SVGInfo icon) 511 { 512 SVGInfo retVal= icon; 513 //safe square inside octagon: <rect x="220" y="310" width="170" height="170"/> 514 double maxSize = 170; 515 RectF bbox = null; 516 if(icon != null) 517 bbox = icon.getBbox(); 518 double length = 0; 519 if(bbox != null) 520 { 521 length = Math.max(bbox.width(), bbox.height()); 522 //adjust max size for narrow, tall icons 523 if(bbox.width() < 60 && bbox.height() > 90) 524 maxSize = 200; 525 526 if(SVGLookup.getMainIconID(symbolID).length() == 8 && length < 145 && length > 0 && 527 bbox.height() < 105 && 528 SymbolID.getCommonModifier1(symbolID)==0 && 529 SymbolID.getCommonModifier2(symbolID)==0 && 530 SymbolID.getModifier1(symbolID)==0 && 531 SymbolID.getModifier2(symbolID)==0)//if largest side smaller than 145 and there are no section mods, make it bigger 532 { 533 double ratio = maxSize / length; 534 double transx = ((bbox.left + (bbox.width()/2)) * ratio) - (bbox.left + (bbox.width()/2)); 535 double transy = ((bbox.top + (bbox.height()/2)) * ratio) - (bbox.top + (bbox.height()/2)); 536 String transform = " transform=\"translate(-" + transx + ",-" + transy + ") scale(" + ratio + " " + ratio + ")\">"; 537 String svg = icon.getSVG(); 538 svg = svg.replaceFirst(">",transform); 539 RectF newBbox = RectUtilities.makeRectF((float)(bbox.left - transx),(float)(bbox.top - transy),(float)(bbox.width() * ratio), (float) (bbox.height() * ratio)); 540 //retVal = new SVGInfo(icon.getID(),newBbox,svg); 541 542 //Adjust stroke widths so they remain the same and don't scale up. 543 int decimals = 3; 544 Pattern pattern = Pattern.compile("stroke-width=\"([\\d.]+)\""); 545 Matcher matcher = pattern.matcher(svg); 546 StringBuffer sb = new StringBuffer(); 547 while (matcher.find()) { 548 double original = Double.parseDouble(matcher.group(1)); 549 double adjusted = original * 1.5 / ratio;//multiply by 1.5 to reduce but not eliminate scaling 550 String replacement = String.format("stroke-width=\"%." + decimals + "f\"", adjusted); 551 matcher.appendReplacement(sb, replacement); 552 } 553 matcher.appendTail(sb); 554 svg = sb.toString();//*/ 555 556 retVal = new SVGInfo(icon.getID(),newBbox,svg); 557 } 558 } 559 560 return retVal; 561 } 562 563 /** 564 * Takes an SVG string and increases all stroke-width values by the increaseBy value. 565 * @param svgString The raw SVG content. 566 * @param increaseBy the number to add to the current stroke value 567 * @return The modified SVG content. 568 */ 569 public static String increaseStrokeWidth(String svgString, int increaseBy) { 570 Pattern pattern = Pattern.compile("stroke-width=\"([^\"]+)\""); 571 Matcher matcher = pattern.matcher(svgString); 572 StringBuilder sb = new StringBuilder(); 573 int lastEnd = 0; 574 575 while (matcher.find()) { 576 // 1. Append everything from the last match up to the current match 577 sb.append(svgString.substring(lastEnd, matcher.start())); 578 579 String replacement; 580 try { 581 // 2. Calculate the new value 582 double currentValue = Double.parseDouble(matcher.group(1)); 583 double newValue = currentValue + increaseBy; 584 String formattedValue = (newValue == (long) newValue) 585 ? String.valueOf((long) newValue) 586 : String.valueOf(newValue); 587 588 replacement = "stroke-width=\"" + formattedValue + "\""; 589 } catch (NumberFormatException e) { 590 // Fallback to original text if not a number 591 replacement = matcher.group(0); 592 } 593 594 // 3. Append the replacement and update our position 595 sb.append(replacement); 596 lastEnd = matcher.end(); 597 } 598 599 // 4. Append any remaining text after the last match 600 sb.append(svgString.substring(lastEnd)); 601 int firstGroup = sb.indexOf("<g"); 602 sb.replace(firstGroup, firstGroup+2,"<g stroke-width=\"" + increaseBy + "\" "); 603 return sb.toString(); 604 } 605 606 public static int getDistanceBetweenPoints(Point pt1, Point pt2) 607 { 608 int distance = (int)(Math.sqrt(Math.pow((pt2.x - pt1.x) ,2) + Math.pow((pt2.y - pt1.y) ,2))); 609 return distance; 610 } 611 612 public static int calculateOutlineWidth() 613 { 614 return RendererSettings.getInstance().getDeviceDPI()>100 ? RendererSettings.getInstance().getDeviceDPI()/96 * 3 : 3; 615 } 616 617 /** 618 * A starting point for calculating map scale. 619 * The User may prefer a different calculation depending on how their maps works. 620 * @param mapPixelWidth Width of your map in pixels 621 * @param eastLon East Longitude of your map 622 * @param westLon West Longitude of your map 623 * @return Map scale value to use in the RenderSymbol function {@link armyc2.c5isr.web.render.WebRenderer#RenderSymbol(String, String, String, String, String, String, double, String, Map, Map, int)} 624 */ 625 public static double calculateMapScale(int mapPixelWidth, double eastLon, double westLon) 626 { 627 return calculateMapScale(mapPixelWidth,eastLon,westLon,RendererSettings.getInstance().getDeviceDPI()); 628 } 629 630 /** 631 * A starting point for calculating map scale. 632 * The User may prefer a different calculation depending on how their maps works. 633 * @param mapPixelWidth Width of your map in pixels 634 * @param eastLon East Longitude of your map 635 * @param westLon West Longitude of your map 636 * @param dpi Dots Per Inch of your device 637 * @return Map scale value to use in the RenderSymbol function {@link armyc2.c5isr.web.render.WebRenderer#RenderSymbol(String, String, String, String, String, String, double, String, Map, Map, int)} 638 */ 639 public static double calculateMapScale(int mapPixelWidth, double eastLon, double westLon, int dpi) 640 { 641 double INCHES_PER_METER = 39.3700787; 642 double METERS_PER_DEG = 40075017.0 / 360.0; // Earth's circumference in meters / 360 degrees 643 644 try 645 { 646 double sizeSquare = Math.abs(eastLon - westLon); 647 if (sizeSquare > 180) 648 sizeSquare = 360 - sizeSquare; 649 650 // physical screen length (in meters) = pixels in screen / pixels per inch / inch per meter 651 double screenLength = mapPixelWidth / dpi / INCHES_PER_METER; 652 // meters on screen = degrees on screen * meters per degree 653 double metersOnScreen = sizeSquare * METERS_PER_DEG; 654 655 double scale = metersOnScreen/screenLength; 656 return scale; 657 } 658 catch(Exception exc) 659 { 660 ErrorLogger.LogException("RendererUtilities","calculateMapScale",exc,Level.WARNING); 661 } 662 return 0; 663 } 664 665 // Overloaded method to return non-outline symbols as normal. 666 public static String setSVGSPCMColors(String symbolID, String svg, Color strokeColor, Color fillColor) { 667 return setSVGSPCMColors(symbolID, svg, strokeColor, fillColor, false,null,0,0); 668 } 669}