/src/gdal/ogr/ogr_srs_esri.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: OpenGIS Simple Features Reference Implementation |
4 | | * Purpose: OGRSpatialReference translation to/from ESRI .prj definitions. |
5 | | * Author: Frank Warmerdam, warmerdam@pobox.com |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2000, Frank Warmerdam |
9 | | * Copyright (c) 2007-2013, Even Rouault <even dot rouault at spatialys.com> |
10 | | * Copyright (c) 2013, Kyle Shannon <kyle at pobox dot com> |
11 | | * |
12 | | * SPDX-License-Identifier: MIT |
13 | | ****************************************************************************/ |
14 | | |
15 | | #include "cpl_port.h" |
16 | | #include "ogr_spatialref.h" |
17 | | #include "ogr_srs_esri_names.h" |
18 | | |
19 | | #include <cmath> |
20 | | #include <climits> |
21 | | #include <cstddef> |
22 | | #include <cstdio> |
23 | | #include <cstdlib> |
24 | | #include <cstring> |
25 | | #include <algorithm> |
26 | | #include <limits> |
27 | | #include <string> |
28 | | |
29 | | #include "cpl_conv.h" |
30 | | #include "cpl_csv.h" |
31 | | #include "cpl_error.h" |
32 | | #include "cpl_multiproc.h" |
33 | | #include "cpl_string.h" |
34 | | #include "cpl_vsi.h" |
35 | | #include "ogr_core.h" |
36 | | #include "ogr_p.h" |
37 | | #include "ogr_srs_api.h" |
38 | | |
39 | | /* -------------------------------------------------------------------- */ |
40 | | /* Table relating USGS and ESRI state plane zones. */ |
41 | | /* -------------------------------------------------------------------- */ |
42 | | constexpr int anUsgsEsriZones[] = { |
43 | | 101, 3101, 102, 3126, 201, 3151, 202, 3176, 203, 3201, 301, 3226, |
44 | | 302, 3251, 401, 3276, 402, 3301, 403, 3326, 404, 3351, 405, 3376, |
45 | | 406, 3401, 407, 3426, 501, 3451, 502, 3476, 503, 3501, 600, 3526, |
46 | | 700, 3551, 901, 3601, 902, 3626, 903, 3576, 1001, 3651, 1002, 3676, |
47 | | 1101, 3701, 1102, 3726, 1103, 3751, 1201, 3776, 1202, 3801, 1301, 3826, |
48 | | 1302, 3851, 1401, 3876, 1402, 3901, 1501, 3926, 1502, 3951, 1601, 3976, |
49 | | 1602, 4001, 1701, 4026, 1702, 4051, 1703, 6426, 1801, 4076, 1802, 4101, |
50 | | 1900, 4126, 2001, 4151, 2002, 4176, 2101, 4201, 2102, 4226, 2103, 4251, |
51 | | 2111, 6351, 2112, 6376, 2113, 6401, 2201, 4276, 2202, 4301, 2203, 4326, |
52 | | 2301, 4351, 2302, 4376, 2401, 4401, 2402, 4426, 2403, 4451, 2500, 0, |
53 | | 2501, 4476, 2502, 4501, 2503, 4526, 2600, 0, 2601, 4551, 2602, 4576, |
54 | | 2701, 4601, 2702, 4626, 2703, 4651, 2800, 4676, 2900, 4701, 3001, 4726, |
55 | | 3002, 4751, 3003, 4776, 3101, 4801, 3102, 4826, 3103, 4851, 3104, 4876, |
56 | | 3200, 4901, 3301, 4926, 3302, 4951, 3401, 4976, 3402, 5001, 3501, 5026, |
57 | | 3502, 5051, 3601, 5076, 3602, 5101, 3701, 5126, 3702, 5151, 3800, 5176, |
58 | | 3900, 0, 3901, 5201, 3902, 5226, 4001, 5251, 4002, 5276, 4100, 5301, |
59 | | 4201, 5326, 4202, 5351, 4203, 5376, 4204, 5401, 4205, 5426, 4301, 5451, |
60 | | 4302, 5476, 4303, 5501, 4400, 5526, 4501, 5551, 4502, 5576, 4601, 5601, |
61 | | 4602, 5626, 4701, 5651, 4702, 5676, 4801, 5701, 4802, 5726, 4803, 5751, |
62 | | 4901, 5776, 4902, 5801, 4903, 5826, 4904, 5851, 5001, 6101, 5002, 6126, |
63 | | 5003, 6151, 5004, 6176, 5005, 6201, 5006, 6226, 5007, 6251, 5008, 6276, |
64 | | 5009, 6301, 5010, 6326, 5101, 5876, 5102, 5901, 5103, 5926, 5104, 5951, |
65 | | 5105, 5976, 5201, 6001, 5200, 6026, 5200, 6076, 5201, 6051, 5202, 6051, |
66 | | 5300, 0, 5400, 0}; |
67 | | |
68 | | /************************************************************************/ |
69 | | /* ESRIToUSGSZone() */ |
70 | | /* */ |
71 | | /* Convert ESRI style state plane zones to USGS style state */ |
72 | | /* plane zones. */ |
73 | | /************************************************************************/ |
74 | | |
75 | | static int ESRIToUSGSZone(int nESRIZone) |
76 | | |
77 | 0 | { |
78 | | // anUsgsEsriZones is a series of ints where 2 consecutive integers |
79 | | // are used to map from USGS to ESRI state plane zones. |
80 | | // TODO(schwehr): Would be better as a std::map. |
81 | 0 | const int nPairs = sizeof(anUsgsEsriZones) / (2 * sizeof(int)); |
82 | |
|
83 | 0 | for (int i = 0; i < nPairs; i++) |
84 | 0 | { |
85 | 0 | if (anUsgsEsriZones[i * 2 + 1] == nESRIZone) |
86 | 0 | return anUsgsEsriZones[i * 2]; |
87 | 0 | } |
88 | | |
89 | 0 | return 0; |
90 | 0 | } |
91 | | |
92 | | /************************************************************************/ |
93 | | /* OSRImportFromESRI() */ |
94 | | /************************************************************************/ |
95 | | |
96 | | /** |
97 | | * \brief Import coordinate system from ESRI .prj format(s). |
98 | | * |
99 | | * This function is the same as the C++ method |
100 | | * OGRSpatialReference::importFromESRI(). |
101 | | */ |
102 | | OGRErr OSRImportFromESRI(OGRSpatialReferenceH hSRS, char **papszPrj) |
103 | | |
104 | 0 | { |
105 | 0 | VALIDATE_POINTER1(hSRS, "OSRImportFromESRI", OGRERR_FAILURE); |
106 | | |
107 | 0 | return reinterpret_cast<OGRSpatialReference *>(hSRS)->importFromESRI( |
108 | 0 | papszPrj); |
109 | 0 | } |
110 | | |
111 | | /************************************************************************/ |
112 | | /* OSR_GDV() */ |
113 | | /* */ |
114 | | /* Fetch a particular parameter out of the parameter list, or */ |
115 | | /* the indicated default if it isn't available. This is a */ |
116 | | /* helper function for importFromESRI(). */ |
117 | | /************************************************************************/ |
118 | | |
119 | | static double OSR_GDV(char **papszNV, const char *pszField, |
120 | | double dfDefaultValue) |
121 | | |
122 | 0 | { |
123 | 0 | if (papszNV == nullptr || papszNV[0] == nullptr) |
124 | 0 | return dfDefaultValue; |
125 | | |
126 | 0 | if (STARTS_WITH_CI(pszField, "PARAM_")) |
127 | 0 | { |
128 | 0 | int iLine = 0; // Used after for loop. |
129 | 0 | for (; papszNV[iLine] != nullptr && |
130 | 0 | !STARTS_WITH_CI(papszNV[iLine], "Paramet"); |
131 | 0 | iLine++) |
132 | 0 | { |
133 | 0 | } |
134 | |
|
135 | 0 | for (int nOffset = atoi(pszField + 6); |
136 | 0 | papszNV[iLine] != nullptr && nOffset > 0; iLine++) |
137 | 0 | { |
138 | 0 | if (strlen(papszNV[iLine]) > 0) |
139 | 0 | nOffset--; |
140 | 0 | } |
141 | |
|
142 | 0 | while (papszNV[iLine] != nullptr && strlen(papszNV[iLine]) == 0) |
143 | 0 | iLine++; |
144 | |
|
145 | 0 | if (papszNV[iLine] != nullptr) |
146 | 0 | { |
147 | 0 | char *const pszLine = papszNV[iLine]; |
148 | | |
149 | | // Trim comments. |
150 | 0 | for (int i = 0; pszLine[i] != '\0'; i++) |
151 | 0 | { |
152 | 0 | if (pszLine[i] == '/' && pszLine[i + 1] == '*') |
153 | 0 | pszLine[i] = '\0'; |
154 | 0 | } |
155 | |
|
156 | 0 | double dfValue = 0.0; |
157 | 0 | char **papszTokens = CSLTokenizeString(papszNV[iLine]); |
158 | 0 | if (CSLCount(papszTokens) == 3) |
159 | 0 | { |
160 | | // http://agdcftp1.wr.usgs.gov/pub/projects/lcc/akcan_lcc/akcan.tar.gz |
161 | | // contains weird values for the second. Ignore it and |
162 | | // the result looks correct. |
163 | 0 | double dfSecond = CPLAtof(papszTokens[2]); |
164 | 0 | if (dfSecond < 0.0 || dfSecond >= 60.0) |
165 | 0 | dfSecond = 0.0; |
166 | |
|
167 | 0 | dfValue = std::abs(CPLAtof(papszTokens[0])) + |
168 | 0 | CPLAtof(papszTokens[1]) / 60.0 + dfSecond / 3600.0; |
169 | |
|
170 | 0 | if (CPLAtof(papszTokens[0]) < 0.0) |
171 | 0 | dfValue *= -1; |
172 | 0 | } |
173 | 0 | else if (CSLCount(papszTokens) > 0) |
174 | 0 | { |
175 | 0 | dfValue = CPLAtof(papszTokens[0]); |
176 | 0 | } |
177 | 0 | else |
178 | 0 | { |
179 | 0 | dfValue = dfDefaultValue; |
180 | 0 | } |
181 | |
|
182 | 0 | CSLDestroy(papszTokens); |
183 | |
|
184 | 0 | return dfValue; |
185 | 0 | } |
186 | | |
187 | 0 | return dfDefaultValue; |
188 | 0 | } |
189 | | |
190 | 0 | int iLine = 0; // Used after for loop. |
191 | 0 | for (; papszNV[iLine] != nullptr && |
192 | 0 | !EQUALN(papszNV[iLine], pszField, strlen(pszField)); |
193 | 0 | iLine++) |
194 | 0 | { |
195 | 0 | } |
196 | |
|
197 | 0 | if (papszNV[iLine] == nullptr) |
198 | 0 | return dfDefaultValue; |
199 | | |
200 | 0 | return CPLAtof(papszNV[iLine] + strlen(pszField)); |
201 | 0 | } |
202 | | |
203 | | /************************************************************************/ |
204 | | /* OSR_GDS() */ |
205 | | /************************************************************************/ |
206 | | |
207 | | static CPLString OSR_GDS(char **papszNV, const char *pszField, |
208 | | const char *pszDefaultValue) |
209 | | |
210 | 0 | { |
211 | 0 | if (papszNV == nullptr || papszNV[0] == nullptr) |
212 | 0 | return pszDefaultValue; |
213 | | |
214 | 0 | int iLine = 0; // Used after for loop. |
215 | 0 | for (; papszNV[iLine] != nullptr && |
216 | 0 | !EQUALN(papszNV[iLine], pszField, strlen(pszField)); |
217 | 0 | iLine++) |
218 | 0 | { |
219 | 0 | } |
220 | |
|
221 | 0 | if (papszNV[iLine] == nullptr) |
222 | 0 | return pszDefaultValue; |
223 | | |
224 | 0 | char **papszTokens = CSLTokenizeString(papszNV[iLine]); |
225 | |
|
226 | 0 | CPLString osResult = |
227 | 0 | CSLCount(papszTokens) > 1 ? papszTokens[1] : pszDefaultValue; |
228 | |
|
229 | 0 | CSLDestroy(papszTokens); |
230 | 0 | return osResult; |
231 | 0 | } |
232 | | |
233 | | /************************************************************************/ |
234 | | /* importFromESRI() */ |
235 | | /************************************************************************/ |
236 | | |
237 | | /** |
238 | | * \brief Import coordinate system from ESRI .prj format(s). |
239 | | * |
240 | | * This function will read the text loaded from an ESRI .prj file, and |
241 | | * translate it into an OGRSpatialReference definition. This should support |
242 | | * many (but by no means all) old style (Arc/Info 7.x) .prj files, as well |
243 | | * as the newer pseudo-OGC WKT .prj files. Note that new style .prj files |
244 | | * are in OGC WKT format, but require some manipulation to correct datum |
245 | | * names, and units on some projection parameters. This is addressed within |
246 | | * importFromESRI() by an automatic call to morphFromESRI(). |
247 | | * |
248 | | * Currently only GEOGRAPHIC, UTM, STATEPLANE, GREATBRITIAN_GRID, ALBERS, |
249 | | * EQUIDISTANT_CONIC, TRANSVERSE (mercator), POLAR, LAMBERT (Conic Conformal), |
250 | | * LAMBERT_AZIMUTHAL, MERCATOR and POLYCONIC |
251 | | * projections are supported from old style files. |
252 | | * |
253 | | * At this time there is no equivalent exportToESRI() method. Writing old |
254 | | * style .prj files is not supported by OGRSpatialReference. However the |
255 | | * morphToESRI() and exportToWkt() methods can be used to generate output |
256 | | * suitable to write to new style (Arc 8) .prj files. |
257 | | * |
258 | | * This function is the equivalent of the C function OSRImportFromESRI(). |
259 | | * |
260 | | * @param papszPrj NULL terminated list of strings containing the definition. |
261 | | * |
262 | | * @return OGRERR_NONE on success or an error code in case of failure. |
263 | | */ |
264 | | |
265 | | OGRErr OGRSpatialReference::importFromESRI(char **papszPrj) |
266 | | |
267 | 0 | { |
268 | 0 | if (papszPrj == nullptr || papszPrj[0] == nullptr) |
269 | 0 | return OGRERR_CORRUPT_DATA; |
270 | | |
271 | | /* -------------------------------------------------------------------- */ |
272 | | /* ArcGIS and related products now use a variant of Well Known */ |
273 | | /* Text. Try to recognize this and ingest it. WKT is usually */ |
274 | | /* all on one line, but we will accept multi-line formats and */ |
275 | | /* concatenate. */ |
276 | | /* -------------------------------------------------------------------- */ |
277 | 0 | if (STARTS_WITH_CI(papszPrj[0], "GEOGCS") || |
278 | 0 | STARTS_WITH_CI(papszPrj[0], "PROJCS") || |
279 | 0 | STARTS_WITH_CI(papszPrj[0], "LOCAL_CS") |
280 | | // Also accept COMPD_CS, even if it is unclear that it is valid |
281 | | // traditional ESRI WKT. But people might use such PRJ file |
282 | | // See https://github.com/OSGeo/gdal/issues/1881 |
283 | 0 | || STARTS_WITH_CI(papszPrj[0], "COMPD_CS")) |
284 | 0 | { |
285 | 0 | std::string osWKT(papszPrj[0]); |
286 | 0 | for (int i = 1; papszPrj[i] != nullptr; i++) |
287 | 0 | { |
288 | 0 | osWKT += papszPrj[i]; |
289 | 0 | } |
290 | 0 | return importFromWkt(osWKT.c_str()); |
291 | 0 | } |
292 | | |
293 | | /* -------------------------------------------------------------------- */ |
294 | | /* Operate on the basis of the projection name. */ |
295 | | /* -------------------------------------------------------------------- */ |
296 | 0 | CPLString osProj = OSR_GDS(papszPrj, "Projection", ""); |
297 | 0 | bool bDatumApplied = false; |
298 | 0 | bool bHasRadiusOfSphereOfReference = false; |
299 | 0 | double dfRadiusOfSphereOfReference = 0.0; |
300 | |
|
301 | 0 | if (EQUAL(osProj, "")) |
302 | 0 | { |
303 | 0 | CPLDebug("OGR_ESRI", "Can't find Projection"); |
304 | 0 | return OGRERR_CORRUPT_DATA; |
305 | 0 | } |
306 | 0 | else if (EQUAL(osProj, "GEOGRAPHIC")) |
307 | 0 | { |
308 | | // Nothing to do. |
309 | 0 | } |
310 | 0 | else if (EQUAL(osProj, "utm")) |
311 | 0 | { |
312 | 0 | const double dfOsrGdv = OSR_GDV(papszPrj, "zone", 0.0); |
313 | 0 | if (dfOsrGdv > 0 && dfOsrGdv < 61) |
314 | 0 | { |
315 | 0 | const double dfYShift = OSR_GDV(papszPrj, "Yshift", 0.0); |
316 | |
|
317 | 0 | SetUTM(static_cast<int>(dfOsrGdv), dfYShift == 0.0); |
318 | 0 | } |
319 | 0 | else |
320 | 0 | { |
321 | 0 | const double dfCentralMeridian = OSR_GDV(papszPrj, "PARAM_1", 0.0); |
322 | 0 | const double dfRefLat = OSR_GDV(papszPrj, "PARAM_2", 0.0); |
323 | 0 | if (dfCentralMeridian >= -180.0 && dfCentralMeridian <= 180.0) |
324 | 0 | { |
325 | 0 | const int nZone = static_cast<int>( |
326 | 0 | (dfCentralMeridian + 183.0) / 6.0 + 0.0000001); |
327 | 0 | SetUTM(nZone, dfRefLat >= 0.0); |
328 | 0 | } |
329 | 0 | } |
330 | 0 | } |
331 | 0 | else if (EQUAL(osProj, "STATEPLANE")) |
332 | 0 | { |
333 | 0 | const double dfZone = OSR_GDV(papszPrj, "zone", 0.0); |
334 | |
|
335 | 0 | if (dfZone < std::numeric_limits<int>::min() || |
336 | 0 | dfZone > std::numeric_limits<int>::max() || std::isnan(dfZone)) |
337 | 0 | { |
338 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "zone out of range: %f", |
339 | 0 | dfZone); |
340 | 0 | return OGRERR_CORRUPT_DATA; |
341 | 0 | } |
342 | | |
343 | 0 | int nZone = static_cast<int>(dfZone); |
344 | |
|
345 | 0 | if (nZone != 0) |
346 | 0 | nZone = ESRIToUSGSZone(nZone); |
347 | 0 | else |
348 | 0 | { |
349 | 0 | const double dfFipszone = OSR_GDV(papszPrj, "fipszone", 0.0); |
350 | |
|
351 | 0 | if (dfFipszone < std::numeric_limits<int>::min() || |
352 | 0 | dfFipszone > std::numeric_limits<int>::max() || |
353 | 0 | std::isnan(dfFipszone)) |
354 | 0 | { |
355 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
356 | 0 | "fipszone out of range: %f", dfFipszone); |
357 | 0 | return OGRERR_CORRUPT_DATA; |
358 | 0 | } |
359 | | |
360 | 0 | nZone = static_cast<int>(dfFipszone); |
361 | 0 | } |
362 | | |
363 | 0 | if (nZone != 0) |
364 | 0 | { |
365 | 0 | bDatumApplied = true; |
366 | 0 | if (EQUAL(OSR_GDS(papszPrj, "Datum", "NAD83"), "NAD27")) |
367 | 0 | SetStatePlane(nZone, FALSE); |
368 | 0 | else |
369 | 0 | SetStatePlane(nZone, TRUE); |
370 | 0 | } |
371 | 0 | } |
372 | 0 | else if (EQUAL(osProj, "GREATBRITIAN_GRID") || |
373 | 0 | EQUAL(osProj, "GREATBRITAIN_GRID")) |
374 | 0 | { |
375 | 0 | const char *pszWkt = |
376 | 0 | "PROJCS[\"OSGB 1936 / British National Grid\"," |
377 | 0 | "GEOGCS[\"OSGB 1936\",DATUM[\"OSGB_1936\"," |
378 | 0 | "SPHEROID[\"Airy 1830\",6377563.396,299.3249646]]," |
379 | 0 | "PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]]," |
380 | 0 | "PROJECTION[\"Transverse_Mercator\"]," |
381 | 0 | "PARAMETER[\"latitude_of_origin\",49]," |
382 | 0 | "PARAMETER[\"central_meridian\",-2]," |
383 | 0 | "PARAMETER[\"scale_factor\",0.999601272]," |
384 | 0 | "PARAMETER[\"false_easting\",400000]," |
385 | 0 | "PARAMETER[\"false_northing\",-100000],UNIT[\"metre\",1]]"; |
386 | |
|
387 | 0 | bDatumApplied = true; |
388 | 0 | importFromWkt(pszWkt); |
389 | 0 | } |
390 | 0 | else if (EQUAL(osProj, "ALBERS")) |
391 | 0 | { |
392 | 0 | SetACEA(OSR_GDV(papszPrj, "PARAM_1", 0.0), |
393 | 0 | OSR_GDV(papszPrj, "PARAM_2", 0.0), |
394 | 0 | OSR_GDV(papszPrj, "PARAM_4", 0.0), |
395 | 0 | OSR_GDV(papszPrj, "PARAM_3", 0.0), |
396 | 0 | OSR_GDV(papszPrj, "PARAM_5", 0.0), |
397 | 0 | OSR_GDV(papszPrj, "PARAM_6", 0.0)); |
398 | 0 | } |
399 | 0 | else if (EQUAL(osProj, "LAMBERT")) |
400 | 0 | { |
401 | 0 | SetLCC(OSR_GDV(papszPrj, "PARAM_1", 0.0), |
402 | 0 | OSR_GDV(papszPrj, "PARAM_2", 0.0), |
403 | 0 | OSR_GDV(papszPrj, "PARAM_4", 0.0), |
404 | 0 | OSR_GDV(papszPrj, "PARAM_3", 0.0), |
405 | 0 | OSR_GDV(papszPrj, "PARAM_5", 0.0), |
406 | 0 | OSR_GDV(papszPrj, "PARAM_6", 0.0)); |
407 | 0 | } |
408 | 0 | else if (EQUAL(osProj, "LAMBERT_AZIMUTHAL")) |
409 | 0 | { |
410 | 0 | for (int iLine = 0; papszPrj[iLine] != nullptr; iLine++) |
411 | 0 | { |
412 | 0 | if (strstr(papszPrj[iLine], "radius of the sphere of reference")) |
413 | 0 | { |
414 | 0 | bHasRadiusOfSphereOfReference = true; |
415 | 0 | break; |
416 | 0 | } |
417 | 0 | } |
418 | 0 | if (bHasRadiusOfSphereOfReference) |
419 | 0 | { |
420 | | // Cf "Workstation" variation of |
421 | | // https://webhelp.esri.com/arcgisdesktop/9.3/index.cfm?TopicName=Lambert_Azimuthal_Equal_Area |
422 | | // that is supposed to be applied to spheres only. |
423 | | // It is also documented at page 71 of |
424 | | // https://kartoweb.itc.nl/geometrics/Map%20projections/Understanding%20Map%20Projections.pdf |
425 | | // ("Understanding Map Projections", Melita Kennedy, ArcInfo 8) |
426 | | // We don't particularly enforce the restriction to spheres, so if |
427 | | // the "Parameters" line had non-spherical axis dimensions, they |
428 | | // would be used. |
429 | | // EPSG has a EPSG:1027 projection method "Lambert Azimuthal Equal Area (Spherical)" |
430 | | // that uses the radius of the authalic sphere, for non-spherical |
431 | | // ellipsoids, but it is not obvious that it would be appropriate here. |
432 | | // Examples: |
433 | | // - https://community.esri.com/t5/data-management-questions/problem-when-projecting-a-raster-file/td-p/400814/page/2 |
434 | | // - https://lists.osgeo.org/pipermail/gdal-dev/2024-May/058990.html |
435 | 0 | dfRadiusOfSphereOfReference = OSR_GDV(papszPrj, "PARAM_1", 0.0); |
436 | 0 | SetLAEA(OSR_GDV(papszPrj, "PARAM_3", 0.0), |
437 | 0 | OSR_GDV(papszPrj, "PARAM_2", 0.0), |
438 | 0 | OSR_GDV(papszPrj, "PARAM_4", 0.0), |
439 | 0 | OSR_GDV(papszPrj, "PARAM_5", 0.0)); |
440 | 0 | } |
441 | 0 | else |
442 | 0 | { |
443 | | // Example: https://trac.osgeo.org/gdal/ticket/4302 |
444 | 0 | SetLAEA(OSR_GDV(papszPrj, "PARAM_2", 0.0), |
445 | 0 | OSR_GDV(papszPrj, "PARAM_1", 0.0), |
446 | 0 | OSR_GDV(papszPrj, "PARAM_3", 0.0), |
447 | 0 | OSR_GDV(papszPrj, "PARAM_4", 0.0)); |
448 | 0 | } |
449 | 0 | } |
450 | 0 | else if (EQUAL(osProj, "EQUIDISTANT_CONIC")) |
451 | 0 | { |
452 | 0 | const double dfStdPCount = OSR_GDV(papszPrj, "PARAM_1", 0.0); |
453 | | // TODO(schwehr): What is a reasonable range for StdPCount? |
454 | 0 | if (dfStdPCount < 0 || dfStdPCount > std::numeric_limits<int>::max() || |
455 | 0 | std::isnan(dfStdPCount)) |
456 | 0 | { |
457 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "StdPCount out of range: %lf", |
458 | 0 | dfStdPCount); |
459 | 0 | return OGRERR_CORRUPT_DATA; |
460 | 0 | } |
461 | 0 | const int nStdPCount = static_cast<int>(dfStdPCount); |
462 | |
|
463 | 0 | if (nStdPCount == 1) |
464 | 0 | { |
465 | 0 | SetEC(OSR_GDV(papszPrj, "PARAM_2", 0.0), |
466 | 0 | OSR_GDV(papszPrj, "PARAM_2", 0.0), |
467 | 0 | OSR_GDV(papszPrj, "PARAM_4", 0.0), |
468 | 0 | OSR_GDV(papszPrj, "PARAM_3", 0.0), |
469 | 0 | OSR_GDV(papszPrj, "PARAM_5", 0.0), |
470 | 0 | OSR_GDV(papszPrj, "PARAM_6", 0.0)); |
471 | 0 | } |
472 | 0 | else |
473 | 0 | { |
474 | 0 | SetEC(OSR_GDV(papszPrj, "PARAM_2", 0.0), |
475 | 0 | OSR_GDV(papszPrj, "PARAM_3", 0.0), |
476 | 0 | OSR_GDV(papszPrj, "PARAM_5", 0.0), |
477 | 0 | OSR_GDV(papszPrj, "PARAM_4", 0.0), |
478 | 0 | OSR_GDV(papszPrj, "PARAM_5", 0.0), |
479 | 0 | OSR_GDV(papszPrj, "PARAM_7", 0.0)); |
480 | 0 | } |
481 | 0 | } |
482 | 0 | else if (EQUAL(osProj, "TRANSVERSE")) |
483 | 0 | { |
484 | 0 | SetTM(OSR_GDV(papszPrj, "PARAM_3", 0.0), |
485 | 0 | OSR_GDV(papszPrj, "PARAM_2", 0.0), |
486 | 0 | OSR_GDV(papszPrj, "PARAM_1", 0.0), |
487 | 0 | OSR_GDV(papszPrj, "PARAM_4", 0.0), |
488 | 0 | OSR_GDV(papszPrj, "PARAM_5", 0.0)); |
489 | 0 | } |
490 | 0 | else if (EQUAL(osProj, "POLAR")) |
491 | 0 | { |
492 | 0 | SetPS(OSR_GDV(papszPrj, "PARAM_2", 0.0), |
493 | 0 | OSR_GDV(papszPrj, "PARAM_1", 0.0), 1.0, |
494 | 0 | OSR_GDV(papszPrj, "PARAM_3", 0.0), |
495 | 0 | OSR_GDV(papszPrj, "PARAM_4", 0.0)); |
496 | 0 | } |
497 | 0 | else if (EQUAL(osProj, "MERCATOR")) |
498 | 0 | { |
499 | 0 | SetMercator2SP(OSR_GDV(papszPrj, "PARAM_2", 0.0), 0.0, |
500 | 0 | OSR_GDV(papszPrj, "PARAM_1", 0.0), |
501 | 0 | OSR_GDV(papszPrj, "PARAM_3", 0.0), |
502 | 0 | OSR_GDV(papszPrj, "PARAM_4", 0.0)); |
503 | 0 | } |
504 | 0 | else if (EQUAL(osProj, SRS_PT_MERCATOR_AUXILIARY_SPHERE)) |
505 | 0 | { |
506 | | // This is EPSG:3875 Pseudo Mercator. We might as well import it from |
507 | | // the EPSG spec. |
508 | 0 | importFromEPSG(3857); |
509 | 0 | bDatumApplied = true; |
510 | 0 | } |
511 | 0 | else if (EQUAL(osProj, "POLYCONIC")) |
512 | 0 | { |
513 | 0 | SetPolyconic(OSR_GDV(papszPrj, "PARAM_2", 0.0), |
514 | 0 | OSR_GDV(papszPrj, "PARAM_1", 0.0), |
515 | 0 | OSR_GDV(papszPrj, "PARAM_3", 0.0), |
516 | 0 | OSR_GDV(papszPrj, "PARAM_4", 0.0)); |
517 | 0 | } |
518 | 0 | else |
519 | 0 | { |
520 | 0 | CPLDebug("OGR_ESRI", "Unsupported projection: %s", osProj.c_str()); |
521 | 0 | SetLocalCS(osProj); |
522 | 0 | } |
523 | | |
524 | | /* -------------------------------------------------------------------- */ |
525 | | /* Try to translate the datum/spheroid. */ |
526 | | /* -------------------------------------------------------------------- */ |
527 | 0 | if (!IsLocal() && !bDatumApplied) |
528 | 0 | { |
529 | 0 | const CPLString osDatum = OSR_GDS(papszPrj, "Datum", ""); |
530 | |
|
531 | 0 | if (EQUAL(osDatum, "NAD27") || EQUAL(osDatum, "NAD83") || |
532 | 0 | EQUAL(osDatum, "WGS84") || EQUAL(osDatum, "WGS72")) |
533 | 0 | { |
534 | 0 | SetWellKnownGeogCS(osDatum); |
535 | 0 | } |
536 | 0 | else if (EQUAL(osDatum, "EUR") || EQUAL(osDatum, "ED50")) |
537 | 0 | { |
538 | 0 | SetWellKnownGeogCS("EPSG:4230"); |
539 | 0 | } |
540 | 0 | else if (EQUAL(osDatum, "GDA94")) |
541 | 0 | { |
542 | 0 | SetWellKnownGeogCS("EPSG:4283"); |
543 | 0 | } |
544 | 0 | else |
545 | 0 | { |
546 | 0 | CPLString osSpheroid = OSR_GDS(papszPrj, "Spheroid", ""); |
547 | |
|
548 | 0 | if (EQUAL(osSpheroid, "INT1909") || |
549 | 0 | EQUAL(osSpheroid, "INTERNATIONAL1909")) |
550 | 0 | { |
551 | 0 | OGRSpatialReference oGCS; |
552 | 0 | oGCS.importFromEPSG(4022); |
553 | 0 | CopyGeogCSFrom(&oGCS); |
554 | 0 | } |
555 | 0 | else if (EQUAL(osSpheroid, "AIRY")) |
556 | 0 | { |
557 | 0 | OGRSpatialReference oGCS; |
558 | 0 | oGCS.importFromEPSG(4001); |
559 | 0 | CopyGeogCSFrom(&oGCS); |
560 | 0 | } |
561 | 0 | else if (EQUAL(osSpheroid, "CLARKE1866")) |
562 | 0 | { |
563 | 0 | OGRSpatialReference oGCS; |
564 | 0 | oGCS.importFromEPSG(4008); |
565 | 0 | CopyGeogCSFrom(&oGCS); |
566 | 0 | } |
567 | 0 | else if (EQUAL(osSpheroid, "GRS80")) |
568 | 0 | { |
569 | 0 | OGRSpatialReference oGCS; |
570 | 0 | oGCS.importFromEPSG(4019); |
571 | 0 | CopyGeogCSFrom(&oGCS); |
572 | 0 | } |
573 | 0 | else if (EQUAL(osSpheroid, "KRASOVSKY") || |
574 | 0 | EQUAL(osSpheroid, "KRASSOVSKY") || |
575 | 0 | EQUAL(osSpheroid, "KRASSOWSKY")) |
576 | 0 | { |
577 | 0 | OGRSpatialReference oGCS; |
578 | 0 | oGCS.importFromEPSG(4024); |
579 | 0 | CopyGeogCSFrom(&oGCS); |
580 | 0 | } |
581 | 0 | else if (EQUAL(osSpheroid, "Bessel")) |
582 | 0 | { |
583 | 0 | OGRSpatialReference oGCS; |
584 | 0 | oGCS.importFromEPSG(4004); |
585 | 0 | CopyGeogCSFrom(&oGCS); |
586 | 0 | } |
587 | 0 | else |
588 | 0 | { |
589 | 0 | bool bFoundParameters = false; |
590 | 0 | for (int iLine = 0; papszPrj[iLine] != nullptr; iLine++) |
591 | 0 | { |
592 | 0 | if (STARTS_WITH_CI(papszPrj[iLine], "Parameters")) |
593 | 0 | { |
594 | 0 | char **papszTokens = CSLTokenizeString( |
595 | 0 | papszPrj[iLine] + strlen("Parameters")); |
596 | 0 | if (CSLCount(papszTokens) == 2) |
597 | 0 | { |
598 | 0 | OGRSpatialReference oGCS; |
599 | 0 | const double dfSemiMajor = CPLAtof(papszTokens[0]); |
600 | 0 | const double dfSemiMinor = CPLAtof(papszTokens[1]); |
601 | 0 | const double dfInvFlattening = |
602 | 0 | OSRCalcInvFlattening(dfSemiMajor, dfSemiMinor); |
603 | 0 | oGCS.SetGeogCS("unknown", "unknown", "unknown", |
604 | 0 | dfSemiMajor, dfInvFlattening); |
605 | 0 | CopyGeogCSFrom(&oGCS); |
606 | 0 | bFoundParameters = true; |
607 | 0 | } |
608 | 0 | CSLDestroy(papszTokens); |
609 | 0 | break; |
610 | 0 | } |
611 | 0 | } |
612 | 0 | if (!bFoundParameters) |
613 | 0 | { |
614 | 0 | if (bHasRadiusOfSphereOfReference) |
615 | 0 | { |
616 | 0 | OGRSpatialReference oGCS; |
617 | 0 | oGCS.SetGeogCS("unknown", "unknown", "unknown", |
618 | 0 | dfRadiusOfSphereOfReference, 0); |
619 | 0 | CopyGeogCSFrom(&oGCS); |
620 | 0 | } |
621 | 0 | else |
622 | 0 | { |
623 | | // If unknown, default to WGS84 so there is something there. |
624 | 0 | SetWellKnownGeogCS("WGS84"); |
625 | 0 | } |
626 | 0 | } |
627 | 0 | } |
628 | 0 | } |
629 | 0 | } |
630 | | |
631 | | /* -------------------------------------------------------------------- */ |
632 | | /* Linear units translation */ |
633 | | /* -------------------------------------------------------------------- */ |
634 | 0 | if (IsLocal() || IsProjected()) |
635 | 0 | { |
636 | 0 | const double dfOldUnits = GetLinearUnits(); |
637 | 0 | const CPLString osValue = OSR_GDS(papszPrj, "Units", ""); |
638 | 0 | CPLString osOldAuth; |
639 | 0 | { |
640 | 0 | const char *pszOldAuth = GetAuthorityCode(nullptr); |
641 | 0 | if (pszOldAuth) |
642 | 0 | osOldAuth = pszOldAuth; |
643 | 0 | } |
644 | |
|
645 | 0 | if (EQUAL(osValue, "")) |
646 | 0 | SetLinearUnitsAndUpdateParameters(SRS_UL_METER, 1.0); |
647 | 0 | else if (EQUAL(osValue, "FEET")) |
648 | 0 | SetLinearUnitsAndUpdateParameters(SRS_UL_US_FOOT, |
649 | 0 | CPLAtof(SRS_UL_US_FOOT_CONV)); |
650 | 0 | else if (CPLAtof(osValue) != 0.0) |
651 | 0 | SetLinearUnitsAndUpdateParameters("user-defined", |
652 | 0 | 1.0 / CPLAtof(osValue)); |
653 | 0 | else |
654 | 0 | SetLinearUnitsAndUpdateParameters(osValue, 1.0); |
655 | | |
656 | | // Reinstall authority if linear units value has not changed (bug #1697) |
657 | 0 | const double dfNewUnits = GetLinearUnits(); |
658 | 0 | if (IsProjected() && !osOldAuth.empty() && dfOldUnits != 0.0 && |
659 | 0 | std::abs(dfNewUnits / dfOldUnits - 1) < 1e-8) |
660 | 0 | { |
661 | 0 | SetAuthority("PROJCS", "EPSG", atoi(osOldAuth)); |
662 | 0 | } |
663 | 0 | } |
664 | |
|
665 | 0 | return OGRERR_NONE; |
666 | 0 | } |
667 | | |
668 | | /************************************************************************/ |
669 | | /* FindCodeFromDict() */ |
670 | | /* */ |
671 | | /* Find the code from a dict file. */ |
672 | | /************************************************************************/ |
673 | | static int FindCodeFromDict(const char *pszDictFile, const char *CSName, |
674 | | char *code) |
675 | 0 | { |
676 | | /* -------------------------------------------------------------------- */ |
677 | | /* Find and open file. */ |
678 | | /* -------------------------------------------------------------------- */ |
679 | 0 | const char *pszFilename = CPLFindFile("gdal", pszDictFile); |
680 | 0 | if (pszFilename == nullptr) |
681 | 0 | return OGRERR_UNSUPPORTED_SRS; |
682 | | |
683 | 0 | VSILFILE *fp = VSIFOpenL(pszFilename, "rb"); |
684 | 0 | if (fp == nullptr) |
685 | 0 | return OGRERR_UNSUPPORTED_SRS; |
686 | | |
687 | | /* -------------------------------------------------------------------- */ |
688 | | /* Process lines. */ |
689 | | /* -------------------------------------------------------------------- */ |
690 | 0 | OGRErr eErr = OGRERR_UNSUPPORTED_SRS; |
691 | 0 | const char *pszLine = nullptr; |
692 | |
|
693 | 0 | while ((pszLine = CPLReadLineL(fp)) != nullptr) |
694 | 0 | { |
695 | 0 | if (pszLine[0] == '#') |
696 | 0 | continue; |
697 | | |
698 | 0 | if (strstr(pszLine, CSName)) |
699 | 0 | { |
700 | 0 | const char *pComma = strchr(pszLine, ','); |
701 | 0 | if (pComma) |
702 | 0 | { |
703 | 0 | strncpy(code, pszLine, pComma - pszLine); |
704 | 0 | code[pComma - pszLine] = '\0'; |
705 | 0 | eErr = OGRERR_NONE; |
706 | 0 | } |
707 | 0 | break; |
708 | 0 | } |
709 | 0 | } |
710 | | |
711 | | /* -------------------------------------------------------------------- */ |
712 | | /* Cleanup */ |
713 | | /* -------------------------------------------------------------------- */ |
714 | 0 | VSIFCloseL(fp); |
715 | |
|
716 | 0 | return eErr; |
717 | 0 | } |
718 | | |
719 | | /************************************************************************/ |
720 | | /* ImportFromESRIStatePlaneWKT() */ |
721 | | /* */ |
722 | | /* Search a ESRI State Plane WKT and import it. */ |
723 | | /************************************************************************/ |
724 | | |
725 | | OGRErr OGRSpatialReference::ImportFromESRIStatePlaneWKT(int code, |
726 | | const char *datumName, |
727 | | const char *unitsName, |
728 | | int pcsCode, |
729 | | const char *csName) |
730 | 0 | { |
731 | | // If the CS name is known. |
732 | 0 | if (code == 0 && !datumName && !unitsName && pcsCode == 32767 && csName) |
733 | 0 | { |
734 | 0 | char codeS[10] = {}; |
735 | 0 | if (FindCodeFromDict("esri_StatePlane_extra.wkt", csName, codeS) != |
736 | 0 | OGRERR_NONE) |
737 | 0 | return OGRERR_FAILURE; |
738 | 0 | return importFromDict("esri_StatePlane_extra.wkt", codeS); |
739 | 0 | } |
740 | | |
741 | 0 | int searchCode = -1; |
742 | 0 | if (unitsName == nullptr) |
743 | 0 | unitsName = ""; |
744 | | |
745 | | // Find state plane prj str by pcs code only. |
746 | 0 | if (code == 0 && !datumName && pcsCode != 32767) |
747 | 0 | { |
748 | 0 | int unitCode = 1; |
749 | 0 | if (EQUAL(unitsName, "international_feet")) |
750 | 0 | unitCode = 3; |
751 | 0 | else if (strstr(unitsName, "feet") || strstr(unitsName, "foot")) |
752 | 0 | unitCode = 2; |
753 | |
|
754 | 0 | for (int i = 0; statePlanePcsCodeToZoneCode[i] != 0; i += 2) |
755 | 0 | { |
756 | 0 | if (pcsCode == statePlanePcsCodeToZoneCode[i]) |
757 | 0 | { |
758 | 0 | searchCode = statePlanePcsCodeToZoneCode[i + 1]; |
759 | 0 | const int unitIndex = searchCode % 10; |
760 | 0 | if ((unitCode == 1 && !(unitIndex == 0 || unitIndex == 1)) || |
761 | 0 | (unitCode == 2 && |
762 | 0 | !(unitIndex == 2 || unitIndex == 3 || unitIndex == 4)) || |
763 | 0 | (unitCode == 3 && !(unitIndex == 5 || unitIndex == 6))) |
764 | 0 | { |
765 | 0 | searchCode -= unitIndex; |
766 | 0 | switch (unitIndex) |
767 | 0 | { |
768 | 0 | case 0: |
769 | 0 | case 3: |
770 | 0 | case 5: |
771 | 0 | if (unitCode == 2) |
772 | 0 | searchCode += 3; |
773 | 0 | else if (unitCode == 3) |
774 | 0 | searchCode += 5; |
775 | 0 | break; |
776 | 0 | case 1: |
777 | 0 | case 2: |
778 | 0 | case 6: |
779 | 0 | if (unitCode == 1) |
780 | 0 | searchCode += 1; |
781 | 0 | if (unitCode == 2) |
782 | 0 | searchCode += 2; |
783 | 0 | else if (unitCode == 3) |
784 | 0 | searchCode += 6; |
785 | 0 | break; |
786 | 0 | case 4: |
787 | | // FIXME? The following cond is not possible: |
788 | | // if( unitCode == 2 ) |
789 | | // searchCode += 4; |
790 | 0 | break; |
791 | 0 | } |
792 | 0 | } |
793 | 0 | break; |
794 | 0 | } |
795 | 0 | } |
796 | 0 | } |
797 | 0 | else // Find state plane prj str by all inputs. |
798 | 0 | { |
799 | 0 | if (code < 0 || code > INT_MAX / 10) |
800 | 0 | return OGRERR_FAILURE; |
801 | | |
802 | | // Need to have a special EPSG-ESRI zone code mapping first. |
803 | 0 | for (int i = 0; statePlaneZoneMapping[i] != 0; i += 3) |
804 | 0 | { |
805 | 0 | if (code == statePlaneZoneMapping[i] && |
806 | 0 | (statePlaneZoneMapping[i + 1] == -1 || |
807 | 0 | pcsCode == statePlaneZoneMapping[i + 1])) |
808 | 0 | { |
809 | 0 | code = statePlaneZoneMapping[i + 2]; |
810 | 0 | break; |
811 | 0 | } |
812 | 0 | } |
813 | 0 | searchCode = code * 10; |
814 | 0 | if (!datumName) |
815 | 0 | { |
816 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "datumName is NULL."); |
817 | 0 | return OGRERR_FAILURE; |
818 | 0 | } |
819 | 0 | if (EQUAL(datumName, "HARN")) |
820 | 0 | { |
821 | 0 | if (EQUAL(unitsName, "international_feet")) |
822 | 0 | searchCode += 5; |
823 | 0 | else if (strstr(unitsName, "feet") || strstr(unitsName, "foot")) |
824 | 0 | searchCode += 3; |
825 | 0 | } |
826 | 0 | else if (strstr(datumName, "NAD") && strstr(datumName, "83")) |
827 | 0 | { |
828 | 0 | if (EQUAL(unitsName, "meters")) |
829 | 0 | searchCode += 1; |
830 | 0 | else if (EQUAL(unitsName, "international_feet")) |
831 | 0 | searchCode += 6; |
832 | 0 | else if (strstr(unitsName, "feet") || strstr(unitsName, "foot")) |
833 | 0 | searchCode += 2; |
834 | 0 | } |
835 | 0 | else if (strstr(datumName, "NAD") && strstr(datumName, "27") && |
836 | 0 | !EQUAL(unitsName, "meters")) |
837 | 0 | { |
838 | 0 | searchCode += 4; |
839 | 0 | } |
840 | 0 | else |
841 | 0 | searchCode = -1; |
842 | 0 | } |
843 | 0 | if (searchCode > 0) |
844 | 0 | { |
845 | 0 | char codeS[20] = {}; |
846 | 0 | snprintf(codeS, sizeof(codeS), "%d", static_cast<int>(searchCode)); |
847 | 0 | return importFromDict("esri_StatePlane_extra.wkt", codeS); |
848 | 0 | } |
849 | 0 | return OGRERR_FAILURE; |
850 | 0 | } |