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