10 #define ModelPixelScaleTag 33550
11 #define ModelTiepointTag 33922
12 #define ModelTransformationTag 34264
13 #define GeoKeyDirectoryTag 34735
14 #define GeoDoubleParamsTag 34736
16 #define GTModelTypeGeoKey 1024
17 #define GTRasterTypeGeoKey 1025
18 #define GeographicTypeGeoKey 2048
19 #define GeogGeodeticDatumGeoKey 2050
20 #define GeogPrimeMeridianGeoKey 2051
21 #define GeogAngularUnitsGeoKey 2054
22 #define GeogEllipsoidGeoKey 2056
23 #define GeogAzimuthUnitsGeoKey 2060
24 #define ProjectedCSTypeGeoKey 3072
25 #define ProjectionGeoKey 3074
26 #define ProjCoordTransGeoKey 3075
27 #define ProjLinearUnitsGeoKey 3076
28 #define ProjStdParallel1GeoKey 3078
29 #define ProjStdParallel2GeoKey 3079
30 #define ProjNatOriginLongGeoKey 3080
31 #define ProjNatOriginLatGeoKey 3081
32 #define ProjFalseEastingGeoKey 3082
33 #define ProjFalseNorthingGeoKey 3083
34 #define ProjFalseOriginLongGeoKey 3084
35 #define ProjFalseOriginLatGeoKey 3085
36 #define ProjFalseOriginEastingGeoKey 3086
37 #define ProjFalseOriginNorthingGeoKey 3087
38 #define ProjCenterLongGeoKey 3088
39 #define ProjCenterLatGeoKey 3089
40 #define ProjCenterEastingGeoKey 3090
41 #define ProjCenterNorthingGeoKey 3091
42 #define ProjScaleAtNatOriginGeoKey 3092
43 #define ProjScaleAtCenterGeoKey 3093
44 #define ProjAzimuthAngleGeoKey 3094
45 #define ProjRectifiedGridAngleGeoKey 3096
47 #define ModelTypeProjected 1
48 #define ModelTypeGeographic 2
49 #define ModelTypeGeocentric 3
51 #define CT_TransverseMercator 1
52 #define CT_ObliqueMercator 3
54 #define CT_LambertConfConic_2SP 8
55 #define CT_LambertConfConic_1SP 9
56 #define CT_LambertAzimEqualArea 10
57 #define CT_AlbersEqualArea 11
60 #define IS_SET(map, key) \
61 ((map).contains(key) && (map).value(key).SHORT != 32767)
65 quint16 KeyDirectoryVersion
;
67 quint16 MinorRevision
;
73 quint16 TIFFTagLocation
;
79 bool GeoTIFF::readEntry(TIFFFile
&file
, Ctx
&ctx
) const
83 quint32 count
, offset
;
85 if (!file
.readValue(tag
))
87 if (!file
.readValue(type
))
89 if (!file
.readValue(count
))
91 if (!file
.readValue(offset
))
95 case ModelPixelScaleTag
:
96 if (type
!= TIFF_DOUBLE
|| count
!= 3)
100 case ModelTiepointTag
:
101 if (type
!= TIFF_DOUBLE
|| count
< 6 || count
% 6)
103 ctx
.tiepoints
= offset
;
104 ctx
.tiepointCount
= count
/ 6;
106 case GeoKeyDirectoryTag
:
107 if (type
!= TIFF_SHORT
)
111 case GeoDoubleParamsTag
:
112 if (type
!= TIFF_DOUBLE
)
116 case ModelTransformationTag
:
117 if (type
!= TIFF_DOUBLE
|| count
!= 16)
126 bool GeoTIFF::readIFD(TIFFFile
&file
, quint32
&offset
, Ctx
&ctx
) const
130 if (!file
.seek(offset
))
132 if (!file
.readValue(count
))
135 for (quint16 i
= 0; i
< count
; i
++)
136 if (!readEntry(file
, ctx
))
139 if (!file
.readValue(offset
))
145 bool GeoTIFF::readScale(TIFFFile
&file
, quint32 offset
, PointD
&scale
) const
147 if (!file
.seek(offset
))
150 if (!file
.readValue(scale
.rx()))
152 if (!file
.readValue(scale
.ry()))
158 bool GeoTIFF::readTiepoints(TIFFFile
&file
, quint32 offset
, quint32 count
,
159 QList
<ReferencePoint
> &points
) const
164 if (!file
.seek(offset
))
167 for (quint32 i
= 0; i
< count
; i
++) {
168 if (!file
.readValue(xy
.rx()))
170 if (!file
.readValue(xy
.ry()))
172 if (!file
.readValue(z
))
175 if (!file
.readValue(pp
.rx()))
177 if (!file
.readValue(pp
.ry()))
179 if (!file
.readValue(pz
))
182 points
.append(ReferencePoint(xy
, pp
));
188 bool GeoTIFF::readMatrix(TIFFFile
&file
, quint32 offset
, double matrix
[16]) const
190 if (!file
.seek(offset
))
193 for (int i
= 0; i
< 16; i
++)
194 if (!file
.readValue(matrix
[i
]))
200 bool GeoTIFF::readKeys(TIFFFile
&file
, Ctx
&ctx
, QMap
<quint16
, Value
> &kv
) const
206 if (!file
.seek(ctx
.keys
))
209 if (!file
.readValue(header
.KeyDirectoryVersion
))
211 if (!file
.readValue(header
.KeyRevision
))
213 if (!file
.readValue(header
.MinorRevision
))
215 if (!file
.readValue(header
.NumberOfKeys
))
218 for (int i
= 0; i
< header
.NumberOfKeys
; i
++) {
219 if (!file
.readValue(entry
.KeyID
))
221 if (!file
.readValue(entry
.TIFFTagLocation
))
223 if (!file
.readValue(entry
.Count
))
225 if (!file
.readValue(entry
.ValueOffset
))
228 switch (entry
.KeyID
) {
229 case GTModelTypeGeoKey
:
230 case GTRasterTypeGeoKey
:
231 case GeographicTypeGeoKey
:
232 case GeogGeodeticDatumGeoKey
:
233 case GeogPrimeMeridianGeoKey
:
234 case GeogAngularUnitsGeoKey
:
235 case GeogAzimuthUnitsGeoKey
:
236 case ProjectedCSTypeGeoKey
:
237 case ProjectionGeoKey
:
238 case ProjCoordTransGeoKey
:
239 case ProjLinearUnitsGeoKey
:
240 case GeogEllipsoidGeoKey
:
241 if (entry
.TIFFTagLocation
!= 0 || entry
.Count
!= 1)
243 value
.SHORT
= entry
.ValueOffset
;
244 kv
.insert(entry
.KeyID
, value
);
246 case ProjScaleAtNatOriginGeoKey
:
247 case ProjNatOriginLongGeoKey
:
248 case ProjNatOriginLatGeoKey
:
249 case ProjFalseEastingGeoKey
:
250 case ProjFalseNorthingGeoKey
:
251 case ProjStdParallel1GeoKey
:
252 case ProjStdParallel2GeoKey
:
253 case ProjCenterLongGeoKey
:
254 case ProjCenterLatGeoKey
:
255 case ProjScaleAtCenterGeoKey
:
256 case ProjAzimuthAngleGeoKey
:
257 case ProjRectifiedGridAngleGeoKey
:
258 case ProjFalseOriginLongGeoKey
:
259 case ProjFalseOriginLatGeoKey
:
260 case ProjCenterEastingGeoKey
:
261 case ProjCenterNorthingGeoKey
:
262 case ProjFalseOriginEastingGeoKey
:
263 case ProjFalseOriginNorthingGeoKey
:
264 if (!readGeoValue(file
, ctx
.values
, entry
.ValueOffset
,
267 kv
.insert(entry
.KeyID
, value
);
277 bool GeoTIFF::readGeoValue(TIFFFile
&file
, quint32 offset
, quint16 index
,
280 qint64 pos
= file
.pos();
282 if (!file
.seek(offset
+ index
* sizeof(double)))
284 if (!file
.readValue(val
))
293 const GCS
*GeoTIFF::gcs(QMap
<quint16
, Value
> &kv
)
297 if (IS_SET(kv
, GeographicTypeGeoKey
)) {
298 if (!(gcs
= GCS::gcs(kv
.value(GeographicTypeGeoKey
).SHORT
)))
299 _errorString
= QString("%1: unknown GCS")
300 .arg(kv
.value(GeographicTypeGeoKey
).SHORT
);
301 } else if (IS_SET(kv
, GeogGeodeticDatumGeoKey
)
302 || kv
.value(GeogEllipsoidGeoKey
).SHORT
== 7019
303 || kv
.value(GeogEllipsoidGeoKey
).SHORT
== 7030) {
304 int pm
= IS_SET(kv
, GeogPrimeMeridianGeoKey
)
305 ? kv
.value(GeogPrimeMeridianGeoKey
).SHORT
: 8901;
306 int au
= IS_SET(kv
, GeogAngularUnitsGeoKey
)
307 ? kv
.value(GeogAngularUnitsGeoKey
).SHORT
: 9102;
309 // If only the ellipsoid is defined and it is GRS80 or WGS84, handle
310 // such definition as a WGS84 geodetic datum.
311 int gd
= IS_SET(kv
, GeogGeodeticDatumGeoKey
)
312 ? kv
.value(GeogGeodeticDatumGeoKey
).SHORT
: 6326;
314 if (!(gcs
= GCS::gcs(gd
, pm
, au
)))
315 _errorString
= QString("%1+%2: unknown geodetic datum + prime"
316 " meridian combination").arg(gd
).arg(pm
);
318 _errorString
= "Can not determine GCS";
323 Projection::Method
GeoTIFF::method(QMap
<quint16
, Value
> &kv
)
325 if (!IS_SET(kv
, ProjCoordTransGeoKey
)) {
326 _errorString
= "Missing coordinate transformation method";
327 return Projection::Method();
330 switch (kv
.value(ProjCoordTransGeoKey
).SHORT
) {
331 case CT_TransverseMercator
:
332 return Projection::Method(9807);
333 case CT_ObliqueMercator
:
334 return Projection::Method(9815);
336 return Projection::Method(9804);
337 case CT_LambertConfConic_2SP
:
338 return Projection::Method(9802);
339 case CT_LambertConfConic_1SP
:
340 return Projection::Method(9801);
341 case CT_LambertAzimEqualArea
:
342 return Projection::Method(9820);
343 case CT_AlbersEqualArea
:
344 return Projection::Method(9822);
346 _errorString
= QString("%1: unknown coordinate transformation method")
347 .arg(kv
.value(ProjCoordTransGeoKey
).SHORT
);
348 return Projection::Method();
352 bool GeoTIFF::projectedModel(QMap
<quint16
, Value
> &kv
)
354 if (IS_SET(kv
, ProjectedCSTypeGeoKey
)) {
356 if (!(pcs
= PCS::pcs(kv
.value(ProjectedCSTypeGeoKey
).SHORT
))) {
357 _errorString
= QString("%1: unknown PCS")
358 .arg(kv
.value(ProjectedCSTypeGeoKey
).SHORT
);
361 _projection
= Projection(pcs
);
362 } else if (IS_SET(kv
, ProjectionGeoKey
)) {
363 const GCS
*g
= gcs(kv
);
366 const PCS
*pcs
= PCS::pcs(g
, kv
.value(ProjectionGeoKey
).SHORT
);
368 _errorString
= QString("%1: unknown projection code")
369 .arg(kv
.value(GeographicTypeGeoKey
).SHORT
)
370 .arg(kv
.value(ProjectionGeoKey
).SHORT
);
373 _projection
= Projection(pcs
);
375 double lat0
, lon0
, scale
, fe
, fn
, sp1
, sp2
;
377 const GCS
*g
= gcs(kv
);
380 Projection::Method
m(method(kv
));
384 AngularUnits
au(IS_SET(kv
, GeogAngularUnitsGeoKey
)
385 ? kv
.value(GeogAngularUnitsGeoKey
).SHORT
: 9102);
386 AngularUnits
zu(IS_SET(kv
, GeogAzimuthUnitsGeoKey
)
387 ? kv
.value(GeogAzimuthUnitsGeoKey
).SHORT
: 9102);
388 LinearUnits
lu(IS_SET(kv
, ProjLinearUnitsGeoKey
)
389 ? kv
.value(ProjLinearUnitsGeoKey
).SHORT
: 9001);
391 _errorString
= QString("%1: unknown projection linear units code")
392 .arg(kv
.value(ProjLinearUnitsGeoKey
).SHORT
);
396 if (kv
.contains(ProjNatOriginLatGeoKey
))
397 lat0
= au
.toDegrees(kv
.value(ProjNatOriginLatGeoKey
).DOUBLE
);
398 else if (kv
.contains(ProjCenterLatGeoKey
))
399 lat0
= au
.toDegrees(kv
.value(ProjCenterLatGeoKey
).DOUBLE
);
400 else if (kv
.contains(ProjFalseOriginLatGeoKey
))
401 lat0
= au
.toDegrees(kv
.value(ProjFalseOriginLatGeoKey
).DOUBLE
);
404 if (kv
.contains(ProjNatOriginLongGeoKey
))
405 lon0
= au
.toDegrees(kv
.value(ProjNatOriginLongGeoKey
).DOUBLE
);
406 else if (kv
.contains(ProjCenterLongGeoKey
))
407 lon0
= au
.toDegrees(kv
.value(ProjCenterLongGeoKey
).DOUBLE
);
408 else if (kv
.contains(ProjFalseOriginLongGeoKey
))
409 lon0
= au
.toDegrees(kv
.value(ProjFalseOriginLongGeoKey
).DOUBLE
);
412 if (kv
.contains(ProjScaleAtNatOriginGeoKey
))
413 scale
= kv
.value(ProjScaleAtNatOriginGeoKey
).DOUBLE
;
414 else if (kv
.contains(ProjScaleAtCenterGeoKey
))
415 scale
= kv
.value(ProjScaleAtCenterGeoKey
).DOUBLE
;
418 if (kv
.contains(ProjStdParallel1GeoKey
))
419 sp1
= au
.toDegrees(kv
.value(ProjStdParallel1GeoKey
).DOUBLE
);
420 else if (kv
.contains(ProjAzimuthAngleGeoKey
))
421 sp1
= zu
.toDegrees(kv
.value(ProjAzimuthAngleGeoKey
).DOUBLE
);
424 if (kv
.contains(ProjStdParallel2GeoKey
))
425 sp2
= au
.toDegrees(kv
.value(ProjStdParallel2GeoKey
).DOUBLE
);
426 else if (kv
.contains(ProjRectifiedGridAngleGeoKey
))
427 sp2
= au
.toDegrees(kv
.value(ProjRectifiedGridAngleGeoKey
).DOUBLE
);
430 if (kv
.contains(ProjFalseNorthingGeoKey
))
431 fn
= lu
.toMeters(kv
.value(ProjFalseNorthingGeoKey
).DOUBLE
);
432 else if (kv
.contains(ProjCenterNorthingGeoKey
))
433 fn
= lu
.toMeters(kv
.value(ProjCenterNorthingGeoKey
).DOUBLE
);
434 else if (kv
.contains(ProjFalseOriginNorthingGeoKey
))
435 fn
= lu
.toMeters(kv
.value(ProjFalseOriginNorthingGeoKey
).DOUBLE
);
438 if (kv
.contains(ProjFalseEastingGeoKey
))
439 fe
= lu
.toMeters(kv
.value(ProjFalseEastingGeoKey
).DOUBLE
);
440 else if (kv
.contains(ProjCenterEastingGeoKey
))
441 fe
= lu
.toMeters(kv
.value(ProjCenterEastingGeoKey
).DOUBLE
);
442 else if (kv
.contains(ProjFalseOriginEastingGeoKey
))
443 fe
= lu
.toMeters(kv
.value(ProjFalseOriginEastingGeoKey
).DOUBLE
);
447 Projection::Setup
setup(lat0
, lon0
, scale
, fe
, fn
, sp1
, sp2
);
448 PCS
pcs(g
, m
, setup
, lu
, CoordinateSystem());
449 _projection
= Projection(&pcs
);
455 bool GeoTIFF::geographicModel(QMap
<quint16
, Value
> &kv
)
457 const GCS
*g
= gcs(kv
);
461 _projection
= Projection(g
);
466 GeoTIFF::GeoTIFF(const QString
&path
)
469 QList
<ReferencePoint
> points
;
471 QMap
<quint16
, Value
> kv
;
476 file
.setFileName(path
);
477 if (!file
.open(QIODevice::ReadOnly
)) {
478 _errorString
= QString("Error opening TIFF file: %1")
479 .arg(file
.errorString());
482 if (!file
.readHeader(ifd
)) {
483 _errorString
= "Invalid TIFF header";
488 if (!readIFD(file
, ifd
, ctx
)) {
489 _errorString
= "Invalid IFD";
495 _errorString
= "Not a GeoTIFF file";
500 if (!readScale(file
, ctx
.scale
, scale
)) {
501 _errorString
= "Error reading model pixel scale";
506 if (!readTiepoints(file
, ctx
.tiepoints
, ctx
.tiepointCount
, points
)) {
507 _errorString
= "Error reading raster->model tiepoint pairs";
512 if (!readKeys(file
, ctx
, kv
)) {
513 _errorString
= "Error reading Geo key/value";
517 switch (kv
.value(GTModelTypeGeoKey
).SHORT
) {
518 case ModelTypeProjected
:
519 if (!projectedModel(kv
))
522 case ModelTypeGeographic
:
523 if (!geographicModel(kv
))
526 case ModelTypeGeocentric
:
527 _errorString
= "Geocentric models are not supported";
530 _errorString
= "Unknown/missing model type";
534 if (ctx
.scale
&& ctx
.tiepoints
)
535 _transform
= Transform(points
.first(), scale
);
536 else if (ctx
.tiepointCount
> 1)
537 _transform
= Transform(points
);
538 else if (ctx
.matrix
) {
540 if (!readMatrix(file
, ctx
.matrix
, matrix
)) {
541 _errorString
= "Error reading transformation matrix";
544 _transform
= Transform(matrix
);
546 _errorString
= "Incomplete/missing model transformation info";
550 if (!_transform
.isValid())
551 _errorString
= _transform
.errorString();