1 /* -*-c++-*- */
2 /* osgEarth - Geospatial SDK for OpenSceneGraph
3 * Copyright 2019 Pelican Mapping
4 * http://osgearth.org
5 *
6 * osgEarth is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
16 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
17 * IN THE SOFTWARE.
18 *
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>
21 */
22 #include "UTMLabelingEngine"
23
24 #define LC "[UTMLabelingEngine] "
25
26 using namespace osgEarth;
27 using namespace osgEarth::Util;
28
29 namespace
30 {
31 // Information for a single UTM zone. The labeling engine supports
32 // two UTM zones (left and right) at a time.
33 struct UTMZone
34 {
35 osg::ref_ptr<const SpatialReference> utmSRS;
36 GeoPoint UL_geo, LL_geo, LR_geo;
37 GeoPoint UL_utm, LL_utm, LR_utm;
38 };
39 }
40
41 //........................................................................
42
UTMLabelingEngine(const SpatialReference * srs)43 UTMLabelingEngine::UTMLabelingEngine(const SpatialReference* srs) :
44 GraticuleLabelingEngine(srs),
45 _maxRes(1.0)
46 {
47 }
48
49 void
setMaxResolution(double value)50 UTMLabelingEngine::setMaxResolution(double value)
51 {
52 _maxRes = osg::maximum(value, 1.0);
53 OE_INFO << LC << "Max resolution = " << _maxRes << std::endl;
54 }
55
56 bool
updateLabels(const osg::Vec3d & LL_world,osg::Vec3d & UL_world,osg::Vec3d & LR_world,ClipSpace & window,CameraData & data)57 UTMLabelingEngine::updateLabels(const osg::Vec3d& LL_world, osg::Vec3d& UL_world, osg::Vec3d& LR_world, ClipSpace& window, CameraData& data)
58 {
59 if (_maxRes > 10000.0)
60 return false;
61
62 // Split the view into (at most) 2 UTM zones.
63 UTMZone left, right;
64
65 left.LL_geo.fromWorld(_srs.get(), LL_world);
66 left.utmSRS = _srs->createUTMFromLonLat(left.LL_geo.x(), left.LL_geo.y());
67 if (left.utmSRS.valid() == false)
68 return false;
69
70 left.UL_geo.fromWorld(_srs.get(), UL_world);
71
72 right.LR_geo.fromWorld(_srs.get(), LR_world);
73 right.utmSRS = _srs->createUTMFromLonLat(right.LR_geo.x(), right.LR_geo.y());
74 if (right.utmSRS.valid() == false)
75 return false;
76
77 bool split = left.utmSRS->isHorizEquivalentTo(right.utmSRS.get()) == false;
78
79 if (split)
80 {
81 // Calculate the longitude of the on-screen zone boundary and fill in the UTMZone values.
82 double splitLon = (::floor(left.LL_geo.x() / 6.0) + 1.0) * 6.0;
83 left.LR_geo.set(_srs.get(), splitLon, left.LL_geo.y(), 0, ALTMODE_ABSOLUTE);
84 right.LL_geo = left.LR_geo;
85 right.UL_geo.set(_srs.get(), splitLon, left.UL_geo.y(), 0, ALTMODE_ABSOLUTE);
86 }
87 else
88 {
89 left.LR_geo = right.LR_geo;
90 }
91
92 left.LR_utm = left.LR_geo.transform(left.utmSRS.get());
93 left.LL_utm = left.LL_geo.transform(left.utmSRS.get());
94 left.UL_utm = left.UL_geo.transform(left.utmSRS.get());
95
96 if (left.LR_utm.isValid() == false ||
97 left.LL_utm.isValid() == false ||
98 left.UL_utm.isValid() == false)
99 {
100 OE_WARN << "Bail: left has invalid coords" << std::endl;
101 return false;
102 }
103
104 if (split)
105 {
106 right.UL_utm = right.UL_geo.transform(right.utmSRS.get());
107 right.LL_utm = right.LL_geo.transform(right.utmSRS.get());
108 right.LR_utm = right.LR_geo.transform(right.utmSRS.get());
109
110 //OE_NOTICE << "Right LL = " << right.LL_utm.toString() << std::endl;
111 if (right.UL_utm.isValid() == false ||
112 right.LL_utm.isValid() == false ||
113 right.LR_utm.isValid() == false)
114 {
115 split = false;
116 }
117 }
118
119 // Vertical extent of the frustum in meters:
120 double utmDiff = left.LL_utm.distanceTo(left.UL_utm);
121
122 // Determine the label interval based on the extent.
123 // These numbers are from trial-and-error.
124 double utmInterval;
125 if (utmDiff > 150000) return false;
126 else if (utmDiff > 18500) utmInterval = osg::maximum(10000.0, _maxRes);
127 else if (utmDiff > 1750) utmInterval = osg::maximum(1000.0, _maxRes);
128 else if (utmDiff > 170) utmInterval = osg::maximum(100.0, _maxRes);
129 else utmInterval = osg::maximum(10.0, _maxRes);
130
131 //OE_NOTICE << "utmDiff=" << utmDiff << ", utmInterval=" << utmInterval << std::endl;
132
133 // Indices into the label pool
134 unsigned xi = 0, yi = 0;
135
136 // Finally, calculate all label positions and update them.
137 // NOTE: It is safe to do this in the CULL traversal since all labels are
138 // dynamic variance AND since all labels are children of this node.
139
140 // LEFT zone:
141 {
142 // Quantize the start location(s) to the interval:
143 double xStart = utmInterval * ::ceil(left.LL_utm.x() / utmInterval);
144
145 unsigned numLabels = left.LL_utm.distanceTo(left.LR_utm) / utmInterval;
146 if (numLabels < 2) numLabels = 2;
147
148 // For now lets just draw 10 labels. Later we'll figure out the proper scale
149 for (unsigned i = 0; i < numLabels && xi < data.xLabels.size(); ++i, ++xi)
150 {
151 double t = (double)i / (double)(numLabels - 1);
152 double x = xStart + utmInterval * i;
153 double y = left.LL_utm.y();
154 GeoPoint p(left.utmSRS.get(), x, y, 0, ALTMODE_ABSOLUTE);
155 int xx = ((int)x % 100000) / utmInterval;
156 window.clampToBottom(p); // also xforms to geographic
157 if (p.y() < 84.0 && p.y() > -80.0)
158 {
159 data.xLabels[xi]->setPosition(p);
160 data.xLabels[xi]->setText(Stringify() << std::setprecision(8) << xx);
161 data.xLabels[xi]->setNodeMask(~0);
162 }
163 }
164
165 double yStart = utmInterval * ::ceil(left.LL_utm.y() / utmInterval);
166
167 numLabels = left.LL_utm.distanceTo(left.UL_utm) / utmInterval;
168 if (numLabels < 2) numLabels = 2;
169
170 for (unsigned i = 0; i < numLabels && yi < data.yLabels.size(); ++i, ++yi)
171 {
172 double t = (double)i / (double)(numLabels - 1);
173 double x = left.LL_utm.x();
174 double y = yStart + utmInterval * i;
175 int yy = ((10000000 + (int)y) % 100000) / utmInterval;
176 GeoPoint p(left.utmSRS.get(), x, y, 0, ALTMODE_ABSOLUTE);
177 window.clampToLeft(p); // also xforms to geographic
178 if (p.y() < 84.0 && p.y() > -80.0)
179 {
180 data.yLabels[yi]->setPosition(p);
181 data.yLabels[yi]->setText(Stringify() << std::setprecision(8) << yy);
182 data.yLabels[yi]->setNodeMask(~0);
183 }
184 }
185 }
186
187 // RIGHT zone, if we are split:
188 if (split)
189 {
190 double xStart = utmInterval * ::ceil(right.LL_utm.x() / utmInterval);
191 //double yStart = utmInterval * ::ceil(right.LL_utm.y() / utmInterval);
192
193 unsigned numLabels = right.LL_utm.distanceTo(right.LR_utm) / utmInterval;
194 if (numLabels < 2) numLabels = 2;
195
196 for (unsigned i = 0; i < numLabels && xi < data.xLabels.size(); ++i, ++xi)
197 {
198 double t = (double)i / (double)(numLabels - 1);
199 double x = xStart + utmInterval * i;
200 double y = right.LL_utm.y();
201 GeoPoint p(right.utmSRS.get(), x, y, 0, ALTMODE_ABSOLUTE);
202 int xx = ((int)x % 100000) / utmInterval;
203 window.clampToBottom(p); // also xforms to geographic
204 if (p.y() < 84.0 && p.y() > -80.0)
205 {
206 data.xLabels[xi]->setPosition(p);
207 data.xLabels[xi]->setText(Stringify() << std::setprecision(8) << xx);
208 data.xLabels[xi]->setNodeMask(~0);
209 }
210 }
211 }
212
213 return true;
214 }
215