1 #include "Projection.h"
2 
3 #include <QRect>
4 #include <QRectF>
5 
6 #include <math.h>
7 
8 // from wikipedia
9 #define EQUATORIALRADIUS 6378137.0
10 #define POLARRADIUS      6356752.0
11 #define EQUATORIALMETERCIRCUMFERENCE  40075016.68
12 #define EQUATORIALMETERHALFCIRCUMFERENCE  20037508.34
13 #define EQUATORIALMETERPERDEGREE    222638.981555556
14 
ProjectionBackend(QString initProjection,std::function<QString (QString)> mapProjectionName)15 ProjectionBackend::ProjectionBackend(QString initProjection, std::function<QString(QString)> mapProjectionName)
16     : ProjectionRevision(0)
17     , IsMercator(false)
18     , IsLatLong(false)
19     , mapProjectionName(mapProjectionName)
20 {
21 
22 
23     projCtx = std::shared_ptr<PJ_CONTEXT>(proj_context_create(), proj_context_destroy);
24 #if defined(Q_OS_WIN)
25     ProjDirs dirs;
26     proj_context_set_search_paths(projCtx.get(), dirs.count, dirs.dirs);
27 #endif // Q_OS_WIN
28     projTransform = std::shared_ptr<PJ>(nullptr);
29     projMutex = std::shared_ptr<QMutex>(new QMutex());
30     setProjectionType(initProjection);
31 }
32 
project(const QPointF & Map) const33 QPointF ProjectionBackend::project(const QPointF & Map) const
34 {
35     if  (IsMercator)
36         return mercatorProject(Map);
37     if  (IsLatLong)
38         return latlonProject(Map);
39     return projProject(Map);
40 }
41 
project(const QLineF & Map) const42 QLineF ProjectionBackend::project(const QLineF & Map) const
43 {
44     return QLineF(project(Map.p1()), project(Map.p2()));
45 }
46 
47 
inverse(const QPointF & projPoint) const48 QPointF ProjectionBackend::inverse(const QPointF & projPoint) const
49 {
50     if (IsLatLong)
51         return latlonInverse(projPoint);
52     if (IsMercator)
53         return mercatorInverse(projPoint);
54     return projInverse(projPoint);
55 }
56 
toProjectedRectF(const QRectF & Viewport,const QRect & screen) const57 QRectF ProjectionBackend::toProjectedRectF(const QRectF& Viewport, const QRect& screen) const
58 {
59     QPointF tl, br;
60     QRectF pViewport;
61 
62     tl = project(Viewport.topLeft());
63     br = project(Viewport.bottomRight());
64     pViewport = QRectF(tl, br);
65 
66     QPointF pCenter(pViewport.center());
67 
68     qreal wv, hv;
69     //wv = (pViewport.width() / Viewport.londiff()) * ((double)screen.width() / Viewport.londiff());
70     //hv = (pViewport.height() / Viewport.latdiff()) * ((double)screen.height() / Viewport.latdiff());
71 
72     qreal Aspect = (double)screen.width() / screen.height();
73     qreal pAspect = fabs(pViewport.width() / pViewport.height());
74 
75     if (pAspect > Aspect) {
76         wv = fabs(pViewport.width());
77         hv = fabs(pViewport.height() * pAspect / Aspect);
78     } else {
79         wv = fabs(pViewport.width() * Aspect / pAspect);
80         hv = fabs(pViewport.height());
81     }
82 
83     pViewport = QRectF((pCenter.x() - wv/2), (pCenter.y() + hv/2), wv, -hv);
84 
85     return pViewport;
86 }
87 
fromProjectedRectF(const QRectF & Viewport) const88 CoordBox ProjectionBackend::fromProjectedRectF(const QRectF& Viewport) const
89 {
90     Coord tl, br;
91     CoordBox bbox;
92 
93     tl = inverse(Viewport.topLeft());
94     br = inverse(Viewport.bottomRight());
95     bbox = CoordBox(tl, br);
96 
97     return bbox;
98 }
99 
projProject(const QPointF & Map) const100 QPointF ProjectionBackend::projProject(const QPointF & Map) const
101 {
102     QMutexLocker locker(projMutex.get());
103     auto trans = proj_trans(projTransform.get(), PJ_DIRECTION::PJ_FWD, {{Map.x(), Map.y(), 0}});
104     //qDebug() << "Project(fromWSG84, " << getProjectionType() << "): " << Map << " -> " << qSetRealNumberPrecision(20) << x << "," << y;
105     return QPointF(trans.xy.x, trans.xy.y);
106 }
107 
projInverse(const QPointF & pProj) const108 Coord ProjectionBackend::projInverse(const QPointF & pProj) const
109 {
110     QMutexLocker locker(projMutex.get());
111     auto trans = proj_trans(projTransform.get(), PJ_DIRECTION::PJ_INV, {{pProj.x(), pProj.y(), 0}});
112     return Coord(trans.xy.x, trans.xy.y);
113 }
114 
projIsLatLong() const115 bool ProjectionBackend::projIsLatLong() const
116 {
117     return IsLatLong;
118 }
119 
getProjection(QString projString)120 PJ* ProjectionBackend::getProjection(QString projString)
121 {
122     QString WGS84("+proj=longlat +ellps=WGS84 +datum=WGS84 +xy_in=deg");
123     PJ* proj = proj_create_crs_to_crs(projCtx.get(), WGS84.toLatin1(), projString.toLatin1(), 0);
124     if (!proj) {
125             qDebug() << "Failed to initialize projection" << WGS84 << "to" << projString << "with error:" << proj_errno_string(proj_errno(nullptr));
126     }
127     return proj;
128 }
129 
setProjectionType(QString aProjectionType)130 bool ProjectionBackend::setProjectionType(QString aProjectionType)
131 {
132     QMutexLocker locker(projMutex.get());
133     if (aProjectionType == projType)
134         return true;
135 
136     projTransform = nullptr;
137 
138     ProjectionRevision++;
139     projType = aProjectionType;
140     projProj4 = QString();
141     IsLatLong = false;
142     IsMercator = false;
143 
144     // Hardcode "Google " projection
145     if (
146             projType.isEmpty() ||
147             projType.toUpper().contains("OSGEO:41001") ||
148             projType.toUpper().contains("EPSG:3785") ||
149             projType.toUpper().contains("EPSG:900913") ||
150             projType.toUpper().contains("EPSG:3857")
151             )
152     {
153         IsMercator = true;
154         projType = "EPSG:3857";
155         return true;
156     }
157     // Hardcode "lat/long " projection
158     if ( projType.toUpper() == "EPSG:4326" )
159     {
160         IsLatLong = true;
161         projType = "EPSG:4326";
162         return true;
163     }
164 
165     projProj4 = mapProjectionName(aProjectionType);
166     projTransform = std::shared_ptr<PJ>(getProjection(projProj4), proj_destroy);
167     if (!projTransform) {
168         // Fall back to the EPSG:3857 and return false. getProjection already logged the error into qDebug().
169         projType = "EPSG:3857";
170         IsMercator = true;
171         return false;
172     }
173     return (projTransform != NULL || IsLatLong || IsMercator);
174 }
175 
getProjectionType() const176 QString ProjectionBackend::getProjectionType() const
177 {
178     return projType;
179 }
180 
getProjectionProj4() const181 QString ProjectionBackend::getProjectionProj4() const
182 {
183   QMutexLocker locker(projMutex.get());
184     if (IsLatLong)
185         return "+init=EPSG:4326";
186     if (IsMercator)
187         return "+init=EPSG:3857";
188     return QString(proj_pj_info(projTransform.get()).definition);
189 }
190 
projectionRevision() const191 int ProjectionBackend::projectionRevision() const
192 {
193     return ProjectionRevision;
194 }
195 
196 // Common routines
197 
latAnglePerM() const198 qreal ProjectionBackend::latAnglePerM() const
199 {
200     qreal LengthOfOneDegreeLat = EQUATORIALRADIUS * M_PI / 180;
201     return 1 / LengthOfOneDegreeLat;
202 }
203 
lonAnglePerM(qreal Lat) const204 qreal ProjectionBackend::lonAnglePerM(qreal Lat) const
205 {
206     qreal LengthOfOneDegreeLat = EQUATORIALRADIUS * M_PI / 180;
207     qreal LengthOfOneDegreeLon = LengthOfOneDegreeLat * fabs(cos(Lat));
208     return 1 / LengthOfOneDegreeLon;
209 }
210 
toXML(QXmlStreamWriter & stream)211 bool ProjectionBackend::toXML(QXmlStreamWriter& stream)
212 {
213     bool OK = true;
214 
215     stream.writeStartElement("Projection");
216     stream.writeAttribute("type", projType);
217     if (!IsLatLong && !IsMercator && !projProj4.isEmpty()) {
218         stream.writeCharacters(projProj4);
219     }
220     stream.writeEndElement();
221 
222 
223     return OK;
224 }
225 
fromXML(QXmlStreamReader & stream)226 void ProjectionBackend::fromXML(QXmlStreamReader& stream)
227 {
228     if (stream.name() == "Projection") {
229         QString proj;
230         if (stream.attributes().hasAttribute("type"))
231             proj = stream.attributes().value("type").toString();
232         else
233             proj = QCoreApplication::translate("Projection", "Document");
234         stream.readNext();
235         if (stream.tokenType() == QXmlStreamReader::Characters) {
236             setProjectionType(stream.text().toString());
237             projType = proj;
238             stream.readNext();
239         } else
240             setProjectionType(proj);
241     }
242 }
243 
mercatorProject(const QPointF & c) const244 QPointF ProjectionBackend::mercatorProject(const QPointF& c) const
245 {
246     qreal x = c.x() / 180. * EQUATORIALMETERHALFCIRCUMFERENCE;
247     qreal y = log(tan(angToRad(c.y())) + 1/cos(angToRad(c.y()))) / M_PI * (EQUATORIALMETERHALFCIRCUMFERENCE);
248 
249     return QPointF(x, y);
250 }
251 
mercatorInverse(const QPointF & point) const252 Coord ProjectionBackend::mercatorInverse(const QPointF& point) const
253 {
254     qreal longitude = point.x()*180.0/EQUATORIALMETERHALFCIRCUMFERENCE;
255     qreal latitude = radToAng(atan(sinh(point.y()/EQUATORIALMETERHALFCIRCUMFERENCE*M_PI)));
256 
257     return Coord(longitude, latitude);
258 }
259