Line data Source code
1 : /*******************************************************************************
2 : * Project: OGR CAD Driver
3 : * Purpose: Implements driver based on libopencad
4 : * Author: Alexandr Borzykh, mush3d at gmail.com
5 : * Author: Dmitry Baryshnikov, polimax@mail.ru
6 : * Language: C++
7 : *******************************************************************************
8 : * The MIT License (MIT)
9 : *
10 : * Copyright (c) 2016 Alexandr Borzykh
11 : * Copyright (c) 2016, NextGIS <info@nextgis.com>
12 : *
13 : * SPDX-License-Identifier: MIT
14 : *******************************************************************************/
15 : #include "cpl_conv.h"
16 : #include "gdal_pam.h"
17 : #include "gdal_proxy.h"
18 : #include "ogr_cad.h"
19 : #include "vsilfileio.h"
20 :
21 : class CADWrapperRasterBand : public GDALProxyRasterBand
22 : {
23 : GDALRasterBand *poBaseBand;
24 :
25 : protected:
26 : virtual GDALRasterBand *
27 : RefUnderlyingRasterBand(bool /* bForceOpen */) const override;
28 :
29 : public:
30 0 : explicit CADWrapperRasterBand(GDALRasterBand *poBaseBandIn)
31 0 : : poBaseBand(poBaseBandIn)
32 : {
33 0 : eDataType = poBaseBand->GetRasterDataType();
34 0 : poBaseBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
35 0 : }
36 : };
37 :
38 : GDALRasterBand *
39 0 : CADWrapperRasterBand::RefUnderlyingRasterBand(bool /* bForceOpen */) const
40 : {
41 0 : return poBaseBand;
42 : }
43 :
44 9 : GDALCADDataset::GDALCADDataset()
45 : : poCADFile(nullptr), papoLayers(nullptr), nLayers(0), poRasterDS(nullptr),
46 9 : poSpatialReference(nullptr)
47 : {
48 9 : }
49 :
50 18 : GDALCADDataset::~GDALCADDataset()
51 : {
52 9 : if (poRasterDS != nullptr)
53 : {
54 0 : GDALClose(poRasterDS);
55 0 : poRasterDS = nullptr;
56 : }
57 :
58 19 : for (int i = 0; i < nLayers; i++)
59 10 : delete papoLayers[i];
60 9 : CPLFree(papoLayers);
61 :
62 9 : if (poSpatialReference)
63 8 : poSpatialReference->Release();
64 :
65 9 : if (poCADFile)
66 8 : delete poCADFile;
67 18 : }
68 :
69 0 : void GDALCADDataset::FillTransform(CADImage *pImage, double dfUnits)
70 : {
71 0 : CADImage::ResolutionUnit eResUnits = pImage->getResolutionUnits();
72 0 : double dfMultiply = 1.0;
73 :
74 0 : switch (eResUnits) // 0 == none, 2 == centimeters, 5 == inches;
75 : {
76 0 : case CADImage::ResolutionUnit::CENTIMETER:
77 0 : dfMultiply = 100.0 / dfUnits; // Meters to linear units
78 0 : break;
79 0 : case CADImage::ResolutionUnit::INCH:
80 0 : dfMultiply = 0.0254 / dfUnits;
81 0 : break;
82 0 : case CADImage::ResolutionUnit::NONE:
83 : default:
84 0 : dfMultiply = 1.0;
85 : }
86 :
87 0 : CADVector oSizePt = pImage->getImageSizeInPx();
88 0 : CADVector oInsPt = pImage->getVertInsertionPoint();
89 0 : CADVector oSizeUnitsPt = pImage->getPixelSizeInACADUnits();
90 0 : m_gt[0] = oInsPt.getX();
91 0 : m_gt[3] = oInsPt.getY() + oSizePt.getY() * oSizeUnitsPt.getX() * dfMultiply;
92 0 : m_gt[2] = 0.0;
93 0 : m_gt[4] = 0.0;
94 :
95 0 : m_gt[1] = oSizeUnitsPt.getX() * dfMultiply;
96 0 : m_gt[5] = -oSizeUnitsPt.getY() * dfMultiply;
97 0 : }
98 :
99 9 : int GDALCADDataset::Open(GDALOpenInfo *poOpenInfo, CADFileIO *pFileIO,
100 : long nSubRasterLayer, long nSubRasterFID)
101 : {
102 9 : osCADFilename = pFileIO->GetFilePath();
103 9 : SetDescription(poOpenInfo->pszFilename);
104 :
105 : const char *papszReadOptions =
106 9 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "MODE", "READ_FAST");
107 18 : const char *papszReadUnsupportedGeoms = CSLFetchNameValueDef(
108 9 : poOpenInfo->papszOpenOptions, "ADD_UNSUPPORTED_GEOMETRIES_DATA", "NO");
109 :
110 9 : enum CADFile::OpenOptions openOpts = CADFile::READ_FAST;
111 9 : bool bReadUnsupportedGeometries = false;
112 9 : if (EQUAL(papszReadOptions, "READ_ALL"))
113 : {
114 0 : openOpts = CADFile::READ_ALL;
115 : }
116 9 : else if (EQUAL(papszReadOptions, "READ_FASTEST"))
117 : {
118 0 : openOpts = CADFile::READ_FASTEST;
119 : }
120 :
121 9 : if (EQUAL(papszReadUnsupportedGeoms, "YES"))
122 : {
123 0 : bReadUnsupportedGeometries = true;
124 : }
125 :
126 9 : poCADFile = OpenCADFile(pFileIO, openOpts, bReadUnsupportedGeometries);
127 :
128 9 : if (GetLastErrorCode() == CADErrorCodes::UNSUPPORTED_VERSION)
129 : {
130 1 : CPLError(CE_Failure, CPLE_NotSupported,
131 : "libopencad %s does not support this version of CAD file.\n"
132 : "Supported formats are:\n%s",
133 : GetVersionString(), GetCADFormats());
134 1 : return FALSE;
135 : }
136 :
137 8 : if (GetLastErrorCode() != CADErrorCodes::SUCCESS)
138 : {
139 0 : CPLError(CE_Failure, CPLE_NotSupported,
140 : "libopencad %s does not support this version of CAD "
141 : "file.\nSupported formats: %s",
142 : GetVersionString(), GetCADFormats());
143 0 : return FALSE;
144 : }
145 :
146 8 : const OGRSpatialReference *poSpatialRef = GetSpatialRef();
147 8 : int nRasters = 1;
148 :
149 8 : if (nSubRasterLayer != -1 && nSubRasterFID != -1)
150 : {
151 : // Indicates that subdataset from CAD layer number nSubRasterLayer and
152 : // FID nSubRasterFID is request
153 0 : nRasters = 2;
154 : }
155 : else
156 : {
157 : // Fill metadata
158 8 : const CADHeader &header = poCADFile->getHeader();
159 576 : for (size_t i = 0; i < header.getSize(); ++i)
160 : {
161 568 : short nCode = header.getCode(static_cast<int>(i));
162 1136 : const CADVariant &oVal = header.getValue(nCode);
163 568 : GDALDataset::SetMetadataItem(header.getValueName(nCode),
164 568 : oVal.getString().c_str());
165 : }
166 :
167 : // Reading content of .prj file, or extracting it from CAD if not
168 : // present
169 8 : nLayers = 0;
170 : // FIXME: We allocate extra memory, do we need more strict policy here?
171 8 : papoLayers = static_cast<OGRCADLayer **>(
172 8 : CPLMalloc(sizeof(OGRCADLayer *) * poCADFile->GetLayersCount()));
173 :
174 8 : int nEncoding = GetCadEncoding();
175 18 : for (size_t i = 0; i < poCADFile->GetLayersCount(); ++i)
176 : {
177 10 : CADLayer &oLayer = poCADFile->GetLayer(i);
178 20 : if (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR &&
179 10 : oLayer.getGeometryCount() > 0)
180 : {
181 : OGRSpatialReference *poSRS =
182 10 : poSpatialRef ? poSpatialRef->Clone() : nullptr;
183 10 : papoLayers[nLayers++] =
184 10 : new OGRCADLayer(this, oLayer, poSRS, nEncoding);
185 10 : if (poSRS)
186 10 : poSRS->Release();
187 : }
188 :
189 10 : if (poOpenInfo->nOpenFlags & GDAL_OF_RASTER)
190 : {
191 10 : for (size_t j = 0; j < oLayer.getImageCount(); ++j)
192 : {
193 0 : nSubRasterLayer = static_cast<long>(i);
194 0 : nSubRasterFID = static_cast<long>(j);
195 0 : GDALDataset::SetMetadataItem(
196 : CPLSPrintf("SUBDATASET_%d_NAME", nRasters),
197 : CPLSPrintf("CAD:%s:%ld:%ld", osCADFilename.c_str(),
198 : nSubRasterLayer, nSubRasterFID),
199 : "SUBDATASETS");
200 0 : GDALDataset::SetMetadataItem(
201 : CPLSPrintf("SUBDATASET_%d_DESC", nRasters),
202 0 : CPLSPrintf("%s - %ld", oLayer.getName().c_str(),
203 : nSubRasterFID),
204 : "SUBDATASETS");
205 0 : nRasters++;
206 : }
207 : }
208 : }
209 : // If nRasters == 2 we have the only one raster in CAD file
210 : }
211 :
212 : // The only one raster layer in dataset is present or subdataset is request
213 8 : if (nRasters == 2)
214 : {
215 0 : CADLayer &oLayer = poCADFile->GetLayer(nSubRasterLayer);
216 0 : CADImage *pImage = oLayer.getImage(nSubRasterFID);
217 0 : if (pImage)
218 : {
219 : // TODO: Add support clipping region in neatline
220 0 : CPLString osImgFilename = pImage->getFilePath();
221 0 : CPLString osImgPath = CPLGetPathSafe(osImgFilename);
222 0 : if (osImgPath.empty())
223 : {
224 : osImgFilename =
225 0 : CPLFormFilenameSafe(CPLGetPathSafe(osCADFilename).c_str(),
226 0 : osImgFilename, nullptr);
227 : }
228 :
229 0 : if (!CPLCheckForFile(const_cast<char *>(osImgFilename.c_str()),
230 : nullptr))
231 0 : return poOpenInfo->nOpenFlags & GDAL_OF_VECTOR;
232 :
233 0 : poRasterDS = GDALDataset::FromHandle(
234 : GDALOpen(osImgFilename, poOpenInfo->eAccess));
235 0 : if (poRasterDS == nullptr)
236 : {
237 0 : delete pImage;
238 0 : return poOpenInfo->nOpenFlags & GDAL_OF_VECTOR;
239 : }
240 0 : if (poRasterDS->GetRasterCount() == 0)
241 : {
242 0 : delete pImage;
243 0 : GDALClose(poRasterDS);
244 0 : return poOpenInfo->nOpenFlags & GDAL_OF_VECTOR;
245 : }
246 :
247 0 : if (poRasterDS->GetGeoTransform(m_gt) != CE_None)
248 : {
249 : // The external world file have priority
250 0 : double dfUnits = 1.0;
251 0 : if (nullptr != poSpatialRef)
252 0 : dfUnits = poSpatialRef->GetLinearUnits();
253 0 : FillTransform(pImage, dfUnits);
254 : }
255 0 : delete pImage;
256 :
257 0 : nRasterXSize = poRasterDS->GetRasterXSize();
258 0 : nRasterYSize = poRasterDS->GetRasterYSize();
259 0 : if (!GDALCheckDatasetDimensions(nRasterXSize, nRasterYSize))
260 : {
261 0 : GDALClose(poRasterDS);
262 0 : return poOpenInfo->nOpenFlags & GDAL_OF_VECTOR;
263 : }
264 :
265 0 : for (int iBand = 1; iBand <= poRasterDS->GetRasterCount(); iBand++)
266 0 : SetBand(iBand, new CADWrapperRasterBand(
267 0 : poRasterDS->GetRasterBand(iBand)));
268 :
269 0 : char **papszDomainList = poRasterDS->GetMetadataDomainList();
270 0 : while (papszDomainList)
271 : {
272 0 : char **papszMetadata = GetMetadata(*papszDomainList);
273 : char **papszRasterMetadata =
274 0 : poRasterDS->GetMetadata(*papszDomainList);
275 0 : if (nullptr == papszMetadata)
276 0 : SetMetadata(papszRasterMetadata, *papszDomainList);
277 : else
278 : {
279 0 : char **papszMD = CSLMerge(CSLDuplicate(papszMetadata),
280 : papszRasterMetadata);
281 0 : SetMetadata(papszMD, *papszDomainList);
282 0 : CSLDestroy(papszMD);
283 : }
284 0 : papszDomainList++;
285 : }
286 : }
287 : }
288 :
289 8 : return TRUE;
290 : }
291 :
292 10 : const OGRLayer *GDALCADDataset::GetLayer(int iLayer) const
293 : {
294 10 : if (iLayer < 0 || iLayer >= nLayers)
295 0 : return nullptr;
296 : else
297 10 : return papoLayers[iLayer];
298 : }
299 :
300 0 : int GDALCADDataset::TestCapability(const char *pszCap) const
301 : {
302 0 : if (EQUAL(pszCap, ODsCCreateLayer) || EQUAL(pszCap, ODsCDeleteLayer))
303 0 : return FALSE;
304 0 : else if (EQUAL(pszCap, ODsCCurveGeometries))
305 0 : return TRUE;
306 0 : else if (EQUAL(pszCap, ODsCMeasuredGeometries))
307 0 : return TRUE;
308 0 : else if (EQUAL(pszCap, ODsCZGeometries))
309 0 : return TRUE;
310 0 : return FALSE;
311 : }
312 :
313 0 : char **GDALCADDataset::GetFileList()
314 : {
315 0 : char **papszFileList = GDALDataset::GetFileList();
316 :
317 : /* duplicated papszFileList = CSLAddString( papszFileList, osCADFilename
318 : * );*/
319 0 : const std::string osPRJFilename = GetPrjFilePath();
320 0 : if (!osPRJFilename.empty())
321 0 : papszFileList = CSLAddString(papszFileList, osPRJFilename.c_str());
322 :
323 0 : for (size_t i = 0; i < poCADFile->GetLayersCount(); ++i)
324 : {
325 0 : CADLayer &oLayer = poCADFile->GetLayer(i);
326 0 : for (size_t j = 0; j < oLayer.getImageCount(); ++j)
327 : {
328 0 : CADImage *pImage = oLayer.getImage(j);
329 0 : if (pImage)
330 : {
331 0 : CPLString osImgFilename = pImage->getFilePath();
332 0 : if (CPLCheckForFile(const_cast<char *>(osImgFilename.c_str()),
333 0 : nullptr) == TRUE)
334 0 : papszFileList = CSLAddString(papszFileList, osImgFilename);
335 : }
336 : }
337 : }
338 :
339 0 : if (nullptr != poRasterDS)
340 : {
341 0 : papszFileList = CSLMerge(papszFileList, poRasterDS->GetFileList());
342 : }
343 0 : return papszFileList;
344 : }
345 :
346 8 : int GDALCADDataset::GetCadEncoding() const
347 : {
348 8 : if (poCADFile == nullptr)
349 0 : return 0;
350 8 : const CADHeader &header = poCADFile->getHeader();
351 : return static_cast<int>(
352 8 : header.getValue(CADHeader::DWGCODEPAGE, 0).getDecimal());
353 : }
354 :
355 8 : const OGRSpatialReference *GDALCADDataset::GetSpatialRef() const
356 : {
357 8 : if (poSpatialReference)
358 0 : return poSpatialReference;
359 :
360 8 : if (poCADFile != nullptr)
361 : {
362 16 : CPLString sESRISpatRef;
363 8 : poSpatialReference = new OGRSpatialReference();
364 8 : poSpatialReference->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
365 :
366 16 : CADDictionary oNOD = poCADFile->GetNOD();
367 24 : CPLString sESRISpatRefData = oNOD.getRecordByName("ESRI_PRJ");
368 8 : if (!sESRISpatRefData.empty())
369 : {
370 : sESRISpatRef =
371 0 : sESRISpatRefData.substr(sESRISpatRefData.find("GEO"));
372 : }
373 :
374 8 : if (!sESRISpatRef.empty())
375 : {
376 0 : char **papszPRJData = nullptr;
377 0 : papszPRJData = CSLAddString(papszPRJData, sESRISpatRef);
378 0 : if (poSpatialReference->importFromESRI(papszPRJData) != OGRERR_NONE)
379 : {
380 0 : CPLError(CE_Warning, CPLE_AppDefined,
381 : "Failed to parse PRJ section, ignoring.");
382 0 : delete poSpatialReference;
383 0 : poSpatialReference = nullptr;
384 : }
385 :
386 0 : CSLDestroy(papszPRJData);
387 : }
388 : else
389 : {
390 16 : const std::string osPRJFilename = GetPrjFilePath();
391 8 : if (!osPRJFilename.empty()) // check if path exists
392 : {
393 0 : CPLPushErrorHandler(CPLQuietErrorHandler);
394 0 : char **papszPRJData = CSLLoad(osPRJFilename.c_str());
395 0 : CPLPopErrorHandler();
396 :
397 0 : if (poSpatialReference->importFromESRI(papszPRJData) !=
398 : OGRERR_NONE)
399 : {
400 0 : CPLError(CE_Warning, CPLE_AppDefined,
401 : "Failed to parse PRJ file, ignoring.");
402 0 : delete poSpatialReference;
403 0 : poSpatialReference = nullptr;
404 : }
405 :
406 0 : if (papszPRJData)
407 0 : CSLDestroy(papszPRJData);
408 : }
409 : }
410 : }
411 :
412 8 : return poSpatialReference;
413 : }
414 :
415 8 : const std::string GDALCADDataset::GetPrjFilePath() const
416 : {
417 16 : std::string osPRJFilename = CPLResetExtensionSafe(osCADFilename, "prj");
418 8 : if (CPLCheckForFile(osPRJFilename.data(), nullptr) == TRUE)
419 0 : return osPRJFilename;
420 :
421 8 : osPRJFilename = CPLResetExtensionSafe(osCADFilename, "PRJ");
422 8 : if (CPLCheckForFile(osPRJFilename.data(), nullptr) == TRUE)
423 0 : return osPRJFilename;
424 :
425 8 : return std::string();
426 : }
427 :
428 0 : CPLErr GDALCADDataset::GetGeoTransform(GDALGeoTransform >) const
429 : {
430 0 : gt = m_gt;
431 0 : return CE_None;
432 : }
433 :
434 0 : int GDALCADDataset::GetGCPCount()
435 : {
436 0 : if (nullptr == poRasterDS)
437 0 : return 0;
438 0 : return poRasterDS->GetGCPCount();
439 : }
440 :
441 0 : const OGRSpatialReference *GDALCADDataset::GetGCPSpatialRef() const
442 : {
443 0 : if (nullptr == poRasterDS)
444 0 : return nullptr;
445 0 : return poRasterDS->GetGCPSpatialRef();
446 : }
447 :
448 0 : const GDAL_GCP *GDALCADDataset::GetGCPs()
449 : {
450 0 : if (nullptr == poRasterDS)
451 0 : return nullptr;
452 0 : return poRasterDS->GetGCPs();
453 : }
454 :
455 0 : int GDALCADDataset::CloseDependentDatasets()
456 : {
457 0 : int bRet = GDALDataset::CloseDependentDatasets();
458 0 : if (poRasterDS != nullptr)
459 : {
460 0 : GDALClose(poRasterDS);
461 0 : poRasterDS = nullptr;
462 0 : bRet = TRUE;
463 : }
464 0 : return bRet;
465 : }
|