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