/src/gdal/ogr/ogrsf_frmts/kml/ogrkmldatasource.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: KML Driver |
4 | | * Purpose: Implementation of OGRKMLDataSource class. |
5 | | * Author: Christopher Condit, condit@sdsc.edu; |
6 | | * Jens Oberender, j.obi@troja.net |
7 | | * |
8 | | ****************************************************************************** |
9 | | * Copyright (c) 2006, Christopher Condit |
10 | | * 2007, Jens Oberender |
11 | | * Copyright (c) 2007-2014, Even Rouault <even dot rouault at spatialys.com> |
12 | | * |
13 | | * SPDX-License-Identifier: MIT |
14 | | ****************************************************************************/ |
15 | | #include "cpl_port.h" |
16 | | #include "ogr_kml.h" |
17 | | |
18 | | #include <cstring> |
19 | | #include <string> |
20 | | |
21 | | #include "cpl_conv.h" |
22 | | #include "cpl_error.h" |
23 | | #include "cpl_minixml.h" |
24 | | #include "cpl_string.h" |
25 | | #include "cpl_vsi.h" |
26 | | #include "cpl_vsi_error.h" |
27 | | #include "ogr_core.h" |
28 | | #include "ogr_spatialref.h" |
29 | | #include "kml.h" |
30 | | #include "kmlutility.h" |
31 | | #include "kmlvector.h" |
32 | | #include "ogrsf_frmts.h" |
33 | | |
34 | | /************************************************************************/ |
35 | | /* OGRKMLDataSource() */ |
36 | | /************************************************************************/ |
37 | | |
38 | 4.70k | OGRKMLDataSource::OGRKMLDataSource() = default; |
39 | | |
40 | | /************************************************************************/ |
41 | | /* ~OGRKMLDataSource() */ |
42 | | /************************************************************************/ |
43 | | |
44 | | OGRKMLDataSource::~OGRKMLDataSource() |
45 | 4.70k | { |
46 | 4.70k | if (fpOutput_ != nullptr) |
47 | 607 | { |
48 | 607 | if (nLayers_ > 0) |
49 | 551 | { |
50 | 551 | if (nLayers_ == 1 && papoLayers_[0]->nWroteFeatureCount_ == 0) |
51 | 12 | { |
52 | 12 | VSIFPrintfL(fpOutput_, "<Folder><name>%s</name>\n", |
53 | 12 | papoLayers_[0]->GetName()); |
54 | 12 | } |
55 | | |
56 | 551 | VSIFPrintfL(fpOutput_, "%s", "</Folder>\n"); |
57 | | |
58 | 2.63k | for (int i = 0; i < nLayers_; i++) |
59 | 2.08k | { |
60 | 2.08k | if (!(papoLayers_[i]->bSchemaWritten_) && |
61 | 1.69k | papoLayers_[i]->nWroteFeatureCount_ != 0) |
62 | 1.11k | { |
63 | 1.11k | CPLString osRet = papoLayers_[i]->WriteSchema(); |
64 | 1.11k | if (!osRet.empty()) |
65 | 1.09k | VSIFPrintfL(fpOutput_, "%s", osRet.c_str()); |
66 | 1.11k | } |
67 | 2.08k | } |
68 | 551 | } |
69 | 607 | VSIFPrintfL(fpOutput_, "%s", "</Document></kml>\n"); |
70 | | |
71 | 607 | VSIFCloseL(fpOutput_); |
72 | 607 | } |
73 | | |
74 | 4.70k | CSLDestroy(papszCreateOptions_); |
75 | 4.70k | CPLFree(pszNameField_); |
76 | 4.70k | CPLFree(pszDescriptionField_); |
77 | 4.70k | CPLFree(pszAltitudeMode_); |
78 | | |
79 | 7.08k | for (int i = 0; i < nLayers_; i++) |
80 | 2.37k | { |
81 | 2.37k | delete papoLayers_[i]; |
82 | 2.37k | } |
83 | | |
84 | 4.70k | CPLFree(papoLayers_); |
85 | | |
86 | 4.70k | #ifdef HAVE_EXPAT |
87 | 4.70k | delete poKMLFile_; |
88 | 4.70k | #endif |
89 | 4.70k | } |
90 | | |
91 | | /************************************************************************/ |
92 | | /* Open() */ |
93 | | /************************************************************************/ |
94 | | |
95 | | #ifdef HAVE_EXPAT |
96 | | int OGRKMLDataSource::Open(const char *pszNewName, int bTestOpen) |
97 | 4.10k | { |
98 | 4.10k | CPLAssert(nullptr != pszNewName); |
99 | | |
100 | | /* -------------------------------------------------------------------- */ |
101 | | /* Create a KML object and open the source file. */ |
102 | | /* -------------------------------------------------------------------- */ |
103 | 4.10k | poKMLFile_ = new KMLVector(); |
104 | | |
105 | 4.10k | if (!poKMLFile_->open(pszNewName)) |
106 | 0 | { |
107 | 0 | delete poKMLFile_; |
108 | 0 | poKMLFile_ = nullptr; |
109 | 0 | return FALSE; |
110 | 0 | } |
111 | | |
112 | | /* -------------------------------------------------------------------- */ |
113 | | /* If we aren't sure it is KML, validate it by start parsing */ |
114 | | /* -------------------------------------------------------------------- */ |
115 | 4.10k | if (bTestOpen && !poKMLFile_->isValid()) |
116 | 3.43k | { |
117 | 3.43k | delete poKMLFile_; |
118 | 3.43k | poKMLFile_ = nullptr; |
119 | 3.43k | return FALSE; |
120 | 3.43k | } |
121 | | |
122 | | /* -------------------------------------------------------------------- */ |
123 | | /* Prescan the KML file so we can later work with the structure */ |
124 | | /* -------------------------------------------------------------------- */ |
125 | 667 | if (!poKMLFile_->parse()) |
126 | 217 | { |
127 | 217 | delete poKMLFile_; |
128 | 217 | poKMLFile_ = nullptr; |
129 | 217 | return FALSE; |
130 | 217 | } |
131 | | |
132 | | /* -------------------------------------------------------------------- */ |
133 | | /* Classify the nodes */ |
134 | | /* -------------------------------------------------------------------- */ |
135 | 450 | if (!poKMLFile_->classifyNodes()) |
136 | 4 | { |
137 | 4 | delete poKMLFile_; |
138 | 4 | poKMLFile_ = nullptr; |
139 | 4 | return FALSE; |
140 | 4 | } |
141 | | |
142 | | /* -------------------------------------------------------------------- */ |
143 | | /* Eliminate the empty containers (if there is at least one */ |
144 | | /* valid container !) */ |
145 | | /* -------------------------------------------------------------------- */ |
146 | 446 | const bool bHasOnlyEmpty = poKMLFile_->hasOnlyEmpty(); |
147 | 446 | if (bHasOnlyEmpty) |
148 | 72 | CPLDebug("KML", "Has only empty containers"); |
149 | 374 | else |
150 | 374 | poKMLFile_->eliminateEmpty(); |
151 | | |
152 | | /* -------------------------------------------------------------------- */ |
153 | | /* Find layers to use in the KML structure */ |
154 | | /* -------------------------------------------------------------------- */ |
155 | 446 | poKMLFile_->findLayers(nullptr, bHasOnlyEmpty); |
156 | | |
157 | 446 | poKMLFile_->findSchemas(); |
158 | | |
159 | | /* -------------------------------------------------------------------- */ |
160 | | /* Print the structure */ |
161 | | /* -------------------------------------------------------------------- */ |
162 | 446 | if (CPLGetConfigOption("KML_DEBUG", nullptr) != nullptr) |
163 | 0 | poKMLFile_->print(3); |
164 | | |
165 | 446 | const int nLayers = poKMLFile_->getNumLayers(); |
166 | | |
167 | | /* -------------------------------------------------------------------- */ |
168 | | /* Allocate memory for the Layers */ |
169 | | /* -------------------------------------------------------------------- */ |
170 | 446 | papoLayers_ = |
171 | 446 | static_cast<OGRKMLLayer **>(CPLMalloc(sizeof(OGRKMLLayer *) * nLayers)); |
172 | | |
173 | 446 | OGRSpatialReference *poSRS = |
174 | 446 | new OGRSpatialReference(SRS_WKT_WGS84_LAT_LONG); |
175 | 446 | poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
176 | | |
177 | 446 | const auto &oMapSchemas = poKMLFile_->GetSchemas(); |
178 | | |
179 | | /* -------------------------------------------------------------------- */ |
180 | | /* Create the Layers and fill them */ |
181 | | /* -------------------------------------------------------------------- */ |
182 | 736 | for (int nCount = 0; nCount < nLayers; nCount++) |
183 | 290 | { |
184 | 290 | if (!poKMLFile_->selectLayer(nCount)) |
185 | 0 | { |
186 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
187 | 0 | "There are no layers or a layer can not be found!"); |
188 | 0 | break; |
189 | 0 | } |
190 | | |
191 | 290 | OGRwkbGeometryType poGeotype = wkbUnknown; |
192 | 290 | if (poKMLFile_->getCurrentType() == Point) |
193 | 50 | poGeotype = wkbPoint; |
194 | 240 | else if (poKMLFile_->getCurrentType() == LineString) |
195 | 18 | poGeotype = wkbLineString; |
196 | 222 | else if (poKMLFile_->getCurrentType() == Polygon) |
197 | 14 | poGeotype = wkbPolygon; |
198 | 208 | else if (poKMLFile_->getCurrentType() == MultiPoint) |
199 | 0 | poGeotype = wkbMultiPoint; |
200 | 208 | else if (poKMLFile_->getCurrentType() == MultiLineString) |
201 | 13 | poGeotype = wkbMultiLineString; |
202 | 195 | else if (poKMLFile_->getCurrentType() == MultiPolygon) |
203 | 0 | poGeotype = wkbMultiPolygon; |
204 | 195 | else if (poKMLFile_->getCurrentType() == MultiGeometry) |
205 | 4 | poGeotype = wkbGeometryCollection; |
206 | | |
207 | 290 | if (poGeotype != wkbUnknown && poKMLFile_->is25D()) |
208 | 43 | poGeotype = wkbSetZ(poGeotype); |
209 | | |
210 | | /* -------------------------------------------------------------------- |
211 | | */ |
212 | | /* Create the layer object. */ |
213 | | /* -------------------------------------------------------------------- |
214 | | */ |
215 | 290 | CPLString sName(poKMLFile_->getCurrentName()); |
216 | | |
217 | 290 | const auto oIterSchema = oMapSchemas.find(sName); |
218 | | |
219 | 290 | if (sName.empty()) |
220 | 9 | { |
221 | 9 | sName.Printf("Layer #%d", nCount); |
222 | 9 | } |
223 | 281 | else |
224 | 281 | { |
225 | | // Build unique layer name |
226 | 281 | int nIter = 2; |
227 | 287 | while (true) |
228 | 287 | { |
229 | 287 | if (GetLayerByName(sName) == nullptr) |
230 | 281 | break; |
231 | 6 | sName = CPLSPrintf("%s (#%d)", |
232 | 6 | poKMLFile_->getCurrentName().c_str(), nIter); |
233 | 6 | nIter++; |
234 | 6 | } |
235 | 281 | } |
236 | | |
237 | 290 | OGRKMLLayer *poLayer = |
238 | 290 | new OGRKMLLayer(sName.c_str(), poSRS, false, poGeotype, this); |
239 | | |
240 | 290 | poLayer->SetLayerNumber(nCount); |
241 | | |
242 | 290 | if (oIterSchema != oMapSchemas.end()) |
243 | 84 | { |
244 | 84 | for (const auto &poFieldDefn : oIterSchema->second) |
245 | 300 | { |
246 | 300 | if (strcmp(poFieldDefn->GetNameRef(), "Name") != 0 && |
247 | 300 | strcmp(poFieldDefn->GetNameRef(), "Description") != 0) |
248 | 300 | { |
249 | 300 | poLayer->GetLayerDefn()->AddFieldDefn(poFieldDefn.get()); |
250 | 300 | } |
251 | 300 | } |
252 | 84 | } |
253 | | |
254 | | /* -------------------------------------------------------------------- |
255 | | */ |
256 | | /* Add layer to data source layer list. */ |
257 | | /* -------------------------------------------------------------------- |
258 | | */ |
259 | 290 | papoLayers_[nCount] = poLayer; |
260 | | |
261 | 290 | nLayers_ = nCount + 1; |
262 | 290 | } |
263 | | |
264 | 446 | poSRS->Release(); |
265 | | |
266 | 446 | return TRUE; |
267 | 450 | } |
268 | | #endif /* HAVE_EXPAT */ |
269 | | |
270 | | /************************************************************************/ |
271 | | /* Create() */ |
272 | | /************************************************************************/ |
273 | | |
274 | | int OGRKMLDataSource::Create(const char *pszName, CSLConstList papszOptions) |
275 | 607 | { |
276 | 607 | CPLAssert(nullptr != pszName); |
277 | | |
278 | 607 | if (fpOutput_ != nullptr) |
279 | 0 | { |
280 | 0 | CPLAssert(false); |
281 | 0 | return FALSE; |
282 | 0 | } |
283 | | |
284 | 607 | if (CSLFetchNameValue(papszOptions, "NameField")) |
285 | 0 | pszNameField_ = CPLStrdup(CSLFetchNameValue(papszOptions, "NameField")); |
286 | 607 | else |
287 | 607 | pszNameField_ = CPLStrdup("Name"); |
288 | | |
289 | 607 | if (CSLFetchNameValue(papszOptions, "DescriptionField")) |
290 | 0 | pszDescriptionField_ = |
291 | 0 | CPLStrdup(CSLFetchNameValue(papszOptions, "DescriptionField")); |
292 | 607 | else |
293 | 607 | pszDescriptionField_ = CPLStrdup("Description"); |
294 | | |
295 | 607 | pszAltitudeMode_ = |
296 | 607 | CPLStrdup(CSLFetchNameValue(papszOptions, "AltitudeMode")); |
297 | 607 | if ((nullptr != pszAltitudeMode_) && strlen(pszAltitudeMode_) > 0) |
298 | 0 | { |
299 | | // Check to see that the specified AltitudeMode is valid |
300 | 0 | if (EQUAL(pszAltitudeMode_, "clampToGround") || |
301 | 0 | EQUAL(pszAltitudeMode_, "relativeToGround") || |
302 | 0 | EQUAL(pszAltitudeMode_, "absolute")) |
303 | 0 | { |
304 | 0 | CPLDebug("KML", "Using '%s' for AltitudeMode", pszAltitudeMode_); |
305 | 0 | } |
306 | 0 | else |
307 | 0 | { |
308 | 0 | CPLFree(pszAltitudeMode_); |
309 | 0 | pszAltitudeMode_ = nullptr; |
310 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
311 | 0 | "Invalid AltitudeMode specified, ignoring"); |
312 | 0 | } |
313 | 0 | } |
314 | 607 | else |
315 | 607 | { |
316 | 607 | CPLFree(pszAltitudeMode_); |
317 | 607 | pszAltitudeMode_ = nullptr; |
318 | 607 | } |
319 | | |
320 | | /* -------------------------------------------------------------------- */ |
321 | | /* Create the output file. */ |
322 | | /* -------------------------------------------------------------------- */ |
323 | | |
324 | 607 | if (strcmp(pszName, "/dev/stdout") == 0) |
325 | 0 | pszName = "/vsistdout/"; |
326 | | |
327 | 607 | fpOutput_ = VSIFOpenExL(pszName, "wb", true); |
328 | 607 | if (fpOutput_ == nullptr) |
329 | 0 | { |
330 | 0 | CPLError(CE_Failure, CPLE_OpenFailed, |
331 | 0 | "Failed to create KML file %s: %s", pszName, |
332 | 0 | VSIGetLastErrorMsg()); |
333 | 0 | return FALSE; |
334 | 0 | } |
335 | | |
336 | | /* -------------------------------------------------------------------- */ |
337 | | /* Write out "standard" header. */ |
338 | | /* -------------------------------------------------------------------- */ |
339 | 607 | VSIFPrintfL(fpOutput_, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"); |
340 | | |
341 | 607 | VSIFPrintfL(fpOutput_, |
342 | 607 | "<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n" |
343 | 607 | "<Document id=\"%s\">\n", |
344 | 607 | CSLFetchNameValueDef(papszOptions, "DOCUMENT_ID", "root_doc")); |
345 | | |
346 | 607 | return TRUE; |
347 | 607 | } |
348 | | |
349 | | /************************************************************************/ |
350 | | /* ICreateLayer() */ |
351 | | /************************************************************************/ |
352 | | |
353 | | OGRLayer * |
354 | | OGRKMLDataSource::ICreateLayer(const char *pszLayerName, |
355 | | const OGRGeomFieldDefn *poGeomFieldDefn, |
356 | | CSLConstList /* papszOptions*/) |
357 | 2.08k | { |
358 | 2.08k | CPLAssert(nullptr != pszLayerName); |
359 | | |
360 | | /* -------------------------------------------------------------------- */ |
361 | | /* Verify we are in update mode. */ |
362 | | /* -------------------------------------------------------------------- */ |
363 | 2.08k | if (fpOutput_ == nullptr) |
364 | 0 | { |
365 | 0 | CPLError(CE_Failure, CPLE_NoWriteAccess, |
366 | 0 | "Data source %s opened for read access. " |
367 | 0 | "New layer %s cannot be created.", |
368 | 0 | GetDescription(), pszLayerName); |
369 | |
|
370 | 0 | return nullptr; |
371 | 0 | } |
372 | | |
373 | 2.08k | const auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; |
374 | 2.08k | const auto poSRS = |
375 | 2.08k | poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; |
376 | | |
377 | | /* -------------------------------------------------------------------- */ |
378 | | /* Close the previous layer (if there is one open) */ |
379 | | /* -------------------------------------------------------------------- */ |
380 | 2.08k | if (GetLayerCount() > 0) |
381 | 1.53k | { |
382 | 1.53k | if (nLayers_ == 1 && papoLayers_[0]->nWroteFeatureCount_ == 0) |
383 | 145 | { |
384 | 145 | VSIFPrintfL(fpOutput_, "<Folder><name>%s</name>\n", |
385 | 145 | papoLayers_[0]->GetName()); |
386 | 145 | } |
387 | | |
388 | 1.53k | VSIFPrintfL(fpOutput_, "</Folder>\n"); |
389 | 1.53k | papoLayers_[GetLayerCount() - 1]->SetClosedForWriting(); |
390 | 1.53k | } |
391 | | |
392 | | /* -------------------------------------------------------------------- */ |
393 | | /* Ensure name is safe as an element name. */ |
394 | | /* -------------------------------------------------------------------- */ |
395 | 2.08k | char *pszCleanLayerName = CPLStrdup(pszLayerName); |
396 | | |
397 | 2.08k | CPLCleanXMLElementName(pszCleanLayerName); |
398 | 2.08k | if (strcmp(pszCleanLayerName, pszLayerName) != 0) |
399 | 811 | { |
400 | 811 | CPLError(CE_Warning, CPLE_AppDefined, |
401 | 811 | "Layer name '%s' adjusted to '%s' for XML validity.", |
402 | 811 | pszLayerName, pszCleanLayerName); |
403 | 811 | } |
404 | | |
405 | 2.08k | if (GetLayerCount() > 0) |
406 | 1.53k | { |
407 | 1.53k | VSIFPrintfL(fpOutput_, "<Folder><name>%s</name>\n", pszCleanLayerName); |
408 | 1.53k | } |
409 | | |
410 | | /* -------------------------------------------------------------------- */ |
411 | | /* Create the layer object. */ |
412 | | /* -------------------------------------------------------------------- */ |
413 | 2.08k | OGRKMLLayer *poLayer = |
414 | 2.08k | new OGRKMLLayer(pszCleanLayerName, poSRS, true, eType, this); |
415 | | |
416 | 2.08k | CPLFree(pszCleanLayerName); |
417 | | |
418 | | /* -------------------------------------------------------------------- */ |
419 | | /* Add layer to data source layer list. */ |
420 | | /* -------------------------------------------------------------------- */ |
421 | 2.08k | papoLayers_ = static_cast<OGRKMLLayer **>( |
422 | 2.08k | CPLRealloc(papoLayers_, sizeof(OGRKMLLayer *) * (nLayers_ + 1))); |
423 | | |
424 | 2.08k | papoLayers_[nLayers_++] = poLayer; |
425 | | |
426 | 2.08k | return poLayer; |
427 | 2.08k | } |
428 | | |
429 | | /************************************************************************/ |
430 | | /* TestCapability() */ |
431 | | /************************************************************************/ |
432 | | |
433 | | int OGRKMLDataSource::TestCapability(const char *pszCap) const |
434 | | |
435 | 5.04k | { |
436 | 5.04k | if (EQUAL(pszCap, ODsCCreateLayer)) |
437 | 2.08k | return TRUE; |
438 | 2.95k | else if (EQUAL(pszCap, ODsCZGeometries)) |
439 | 0 | return TRUE; |
440 | | |
441 | 2.95k | return FALSE; |
442 | 5.04k | } |
443 | | |
444 | | /************************************************************************/ |
445 | | /* GetLayer() */ |
446 | | /************************************************************************/ |
447 | | |
448 | | const OGRLayer *OGRKMLDataSource::GetLayer(int iLayer) const |
449 | 19.8k | { |
450 | 19.8k | if (iLayer < 0 || iLayer >= nLayers_) |
451 | 0 | return nullptr; |
452 | | |
453 | 19.8k | return papoLayers_[iLayer]; |
454 | 19.8k | } |
455 | | |
456 | | /************************************************************************/ |
457 | | /* GrowExtents() */ |
458 | | /************************************************************************/ |
459 | | |
460 | | void OGRKMLDataSource::GrowExtents(OGREnvelope *psGeomBounds) |
461 | 28.3k | { |
462 | 28.3k | CPLAssert(nullptr != psGeomBounds); |
463 | | |
464 | 28.3k | oEnvelope_.Merge(*psGeomBounds); |
465 | 28.3k | } |