]> gerrit.simantics Code Review - simantics/district.git/blob - org.simantics.district.maps/src/org/simantics/maps/sg/MapScaleNode.java
Optimization of district scene graph node rendering
[simantics/district.git] / org.simantics.district.maps / src / org / simantics / maps / sg / MapScaleNode.java
1 package org.simantics.maps.sg;
2
3 import java.awt.AlphaComposite;
4 import java.awt.Color;
5 import java.awt.Composite;
6 import java.awt.Font;
7 import java.awt.FontMetrics;
8 import java.awt.Graphics2D;
9 import java.awt.geom.AffineTransform;
10 import java.awt.geom.Rectangle2D;
11
12 import org.geotools.referencing.CRS;
13 import org.geotools.referencing.GeodeticCalculator;
14 import org.opengis.referencing.FactoryException;
15 import org.opengis.referencing.crs.CoordinateReferenceSystem;
16 import org.simantics.maps.sg.Formatting.FormatMode;
17 import org.simantics.scenegraph.utils.DPIUtil;
18 import org.slf4j.Logger;
19 import org.slf4j.LoggerFactory;
20
21 public class MapScaleNode extends MapInfoNode {
22
23     private static final Logger LOGGER = LoggerFactory.getLogger(MapScaleNode.class);
24
25     private static final long serialVersionUID = -2738682328944298290L;
26
27     private static final Color GRAY = new Color(50, 50, 50);
28
29     private static final transient int    SCALE_VERTICAL_MARGIN      = 4;
30     private static final transient int    SCALE_MAJOR_TICK_HEIGHT    = 8;
31     private static final transient int    SCALE_MINOR_TICK_HEIGHT    = 5;
32     private static final transient int    SCALE_MICRO_TICK_HEIGHT    = 3;
33     private static final transient double SCALE_RIGHT_MARGIN         = 20;
34
35     private static final transient int      MAX_DIGITS               = 0;
36     private static final transient double   TRIM_THRESHOLD_MAX_VALUE = Math.pow(10, 2);
37     private static final transient String[] SI_LENGTH_UNIT           = { "m", "km" };
38
39     private static final transient double[] SCALE_VALUES = { 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000, 2000000, 5000000, 10000000, 20000000, 50000000 };
40
41     private GeodeticCalculator calculator;
42
43     @Override
44     public void render(Graphics2D g) {
45         if (!enabled || calculator == null)
46             return;
47
48         AffineTransform ot = g.getTransform();
49         g.transform(transform);
50
51         AffineTransform tr = g.getTransform();
52         double scaleX = Math.abs(tr.getScaleX());
53         double scaleY = Math.abs(tr.getScaleY());
54         if (scaleX <= 0 || scaleY <= 0) {
55             // Make sure that we don't end up in an eternal loop below.
56             return;
57         }
58         double offsetX = tr.getTranslateX();
59         double offsetY = tr.getTranslateY();
60         g.setTransform(new AffineTransform());
61
62         Rectangle2D controlBounds = g.getClipBounds();
63         if (controlBounds == null)
64             return; // FIXME
65
66         Font font = MapInfoConstants.getInfoFont();
67         g.setFont(font);
68         FontMetrics fm = g.getFontMetrics();
69
70         int majorTickHeight = DPIUtil.upscale(SCALE_MAJOR_TICK_HEIGHT);
71         int minorTickHeight = DPIUtil.upscale(SCALE_MINOR_TICK_HEIGHT);
72         int microTickHeight = DPIUtil.upscale(SCALE_MICRO_TICK_HEIGHT);
73         double yOffsetFromBottom = getMapInfoNextY(g);
74
75         double scaleTotalHeight = Math.max(majorTickHeight + SCALE_VERTICAL_MARGIN * 2 , font.getSize() + MapInfoConstants.TEXT_VERTICAL_MARGIN * 2);
76         double scaleMaxX = controlBounds.getMaxX() - SCALE_RIGHT_MARGIN;
77         double scaleMaxY = controlBounds.getMaxY() - yOffsetFromBottom;
78         double scaleMinY = scaleMaxY - scaleTotalHeight;
79         int textY = (int) scaleMinY + MapInfoConstants.TEXT_VERTICAL_MARGIN + fm.getMaxAscent();
80         double meterPerPixel = getMeterPerPixel(scaleMaxX - offsetX, scaleMinY - offsetY, scaleX, scaleY);
81
82         // Prevent the scale from getting too long in the X-direction
83         if (meterPerPixel < 0.005)
84             return;
85
86         double scaleWidth = findScaleWidth(meterPerPixel);
87         double scaleMinX = scaleMaxX - scaleWidth;
88         double value = scaleWidth * meterPerPixel;
89         String formattedValue = formatValue(value);
90         Rectangle2D textBounds = fm.getStringBounds(formattedValue, g);
91         double addedTextWidth = textBounds.getWidth() + MapInfoConstants.TEXT_HORIZONTAL_MARGIN * 2;
92         scaleMinX -= addedTextWidth;
93         scaleMaxX -= addedTextWidth;
94         rect.setFrame(scaleMinX, scaleMinY, scaleWidth + addedTextWidth, scaleTotalHeight);
95
96 //        System.out.println("----");
97 //        System.out.println("scale: " + scaleX + ", " + scaleY);
98 //        System.out.println("scaleMaxX: " + scaleMaxX + ", offsetX: " + offsetX);
99 //        System.out.println("scaleMinY: " + scaleMinY + ", offsetY: " + offsetY);
100 //        System.out.println("meterPerPixel: " + meterPerPixel);
101
102         Composite oc = g.getComposite();
103         g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f));
104         g.setStroke(MapInfoConstants.INFO_STROKE);
105
106         g.setColor(MapInfoConstants.TEXT_BG_COLOR);
107         g.fill(rect);
108
109         g.setColor(Color.BLACK);
110         g.drawString(formattedValue, (int) (scaleMinX + scaleWidth) + MapInfoConstants.TEXT_HORIZONTAL_MARGIN, textY);
111
112         double stepX = scaleWidth;
113
114         g.setColor(GRAY);
115         int yOffset = -SCALE_VERTICAL_MARGIN;
116         g.drawLine((int) scaleMinX, (int) scaleMaxY + yOffset + 1, (int) scaleMaxX, (int) scaleMaxY + yOffset + 1);
117         g.drawLine((int) scaleMinX, (int) scaleMaxY - majorTickHeight + yOffset, (int) scaleMinX, (int) scaleMaxY + yOffset);
118         g.drawLine((int) scaleMaxX, (int) scaleMaxY - majorTickHeight + yOffset, (int) scaleMaxX, (int) scaleMaxY + yOffset);
119         double x = scaleMinX;
120         if (stepX / 5 > 2) {
121             for (double x2 = x + stepX / 5; x2 < x + stepX; x2 += stepX / 5) {
122                 if (x2 > 20) {
123                     g.drawLine((int) x2, (int) scaleMaxY - minorTickHeight + yOffset, (int) x2, (int) scaleMaxY + yOffset);
124                 }
125             }
126             for (double x2 = x + stepX / 10; x2 < x + stepX; x2 += stepX / 5) {
127                 if (x2 > 20) {
128                     g.drawLine((int) x2, (int) scaleMaxY - microTickHeight + yOffset, (int) x2, (int) scaleMaxY + yOffset);
129                 }
130             }
131         }
132
133         g.setComposite(oc);
134         g.setTransform(ot);
135
136         setMapInfoNextY(g, yOffsetFromBottom + scaleTotalHeight + MapInfoConstants.INFO_ROW_SPACING);
137     }
138
139     private String formatValue(double value) {
140         int lengthUnit = 0;
141         if (value > 1000.0) {
142             value /= 1000.0;
143             lengthUnit = 1;
144         }
145         return Formatting.formatValue(value, MAX_DIGITS, true, FormatMode.LIMIT_DECIMALS, TRIM_THRESHOLD_MAX_VALUE, false)
146                 + " " + SI_LENGTH_UNIT[lengthUnit];
147     }
148
149     @Override
150     public void init() {
151         try {
152             CoordinateReferenceSystem EPSG4326 = CRS.decode("EPSG:4326");
153             calculator = new GeodeticCalculator(EPSG4326);
154         } catch (FactoryException e) {
155             LOGGER.error("Failed to initialize GeodeticCalculator with coordinate reference system EPSG:4326", e);
156         }
157         super.init();
158     }
159
160     private double getMeterPerPixel(double screenX, double screenY, double scaleX, double scaleY) {
161         double startLon = (screenX / scaleX);
162         double val = (screenY / scaleY);
163         val = Math.toDegrees(Math.atan(Math.sinh(Math.toRadians(val))));
164         double startLat = val;
165         double endLon = ((screenX + 1) / scaleX);
166         double endLat = val;
167
168         calculator.setStartingGeographicPoint(startLon, startLat);
169         calculator.setDestinationGeographicPoint(endLon, endLat);
170         double distance = calculator.getOrthodromicDistance();
171
172         return distance;
173     }
174
175     private double findScaleWidth(double meterPerPixel) {
176         double scaleWidth = 0;
177         for (int i = 0; i < SCALE_VALUES.length; i++) {
178             scaleWidth = SCALE_VALUES[i] / meterPerPixel;
179             if (scaleWidth > 100)
180                 break;
181         }
182         return scaleWidth;
183     }
184
185 }