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