Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: KML Driver
4 : * Purpose: Implementation of OGRKMLLayer class.
5 : * Author: Christopher Condit, condit@sdsc.edu
6 : * Jens Oberender, j.obi@troja.net
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2006, Christopher Condit
10 : * Copyright (c) 2007-2014, Even Rouault <even dot rouault at spatialys.com>
11 : *
12 : * SPDX-License-Identifier: MIT
13 : ****************************************************************************/
14 :
15 : #include "cpl_port.h"
16 : #include "ogr_kml.h"
17 :
18 : #include <string>
19 :
20 : #include "cpl_conv.h"
21 : #include "cpl_error.h"
22 : #include "cpl_string.h"
23 : #include "cpl_vsi.h"
24 : #include "kml.h"
25 : #include "kmlutility.h"
26 : #include "ogr_api.h"
27 : #include "ogr_core.h"
28 : #include "ogr_feature.h"
29 : #include "ogr_featurestyle.h"
30 : #include "ogr_geometry.h"
31 : #include "ogr_p.h"
32 : #include "ogr_spatialref.h"
33 : #include "ogrsf_frmts.h"
34 :
35 : /* Function utility to dump OGRGeometry to KML text. */
36 : char *OGR_G_ExportToKML(OGRGeometryH hGeometry, const char *pszAltitudeMode);
37 :
38 : /************************************************************************/
39 : /* OGRKMLLayer() */
40 : /************************************************************************/
41 :
42 142 : OGRKMLLayer::OGRKMLLayer(const char *pszName,
43 : const OGRSpatialReference *poSRSIn, bool bWriterIn,
44 142 : OGRwkbGeometryType eReqType, OGRKMLDataSource *poDSIn)
45 : : poDS_(poDSIn),
46 142 : poSRS_(poSRSIn ? new OGRSpatialReference(nullptr) : nullptr),
47 142 : poFeatureDefn_(new OGRFeatureDefn(pszName)), bWriter_(bWriterIn),
48 426 : pszName_(CPLStrdup(pszName))
49 : {
50 : // KML should be created as WGS84.
51 142 : if (poSRSIn != nullptr)
52 : {
53 84 : poSRS_->SetWellKnownGeogCS("WGS84");
54 84 : poSRS_->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
55 84 : if (!poSRS_->IsSame(poSRSIn))
56 : {
57 1 : poCT_ = OGRCreateCoordinateTransformation(poSRSIn, poSRS_);
58 1 : if (poCT_ == nullptr && poDSIn->IsFirstCTError())
59 : {
60 : // If we can't create a transformation, issue a warning - but
61 : // continue the transformation.
62 0 : char *pszWKT = nullptr;
63 :
64 0 : poSRSIn->exportToPrettyWkt(&pszWKT, FALSE);
65 :
66 0 : CPLError(
67 : CE_Warning, CPLE_AppDefined,
68 : "Failed to create coordinate transformation between the "
69 : "input coordinate system and WGS84. This may be because "
70 : "they are not transformable. "
71 : "KML geometries may not render correctly. "
72 : "This message will not be issued any more."
73 : "\nSource:\n%s\n",
74 : pszWKT);
75 :
76 0 : CPLFree(pszWKT);
77 0 : poDSIn->IssuedFirstCTError();
78 : }
79 : }
80 : }
81 :
82 142 : SetDescription(poFeatureDefn_->GetName());
83 142 : poFeatureDefn_->Reference();
84 142 : poFeatureDefn_->SetGeomType(eReqType);
85 142 : if (poFeatureDefn_->GetGeomFieldCount() != 0)
86 138 : poFeatureDefn_->GetGeomFieldDefn(0)->SetSpatialRef(poSRS_);
87 :
88 284 : OGRFieldDefn oFieldName("Name", OFTString);
89 142 : poFeatureDefn_->AddFieldDefn(&oFieldName);
90 :
91 142 : OGRFieldDefn oFieldDesc("Description", OFTString);
92 142 : poFeatureDefn_->AddFieldDefn(&oFieldDesc);
93 :
94 142 : bClosedForWriting = !bWriterIn;
95 142 : }
96 :
97 : /************************************************************************/
98 : /* ~OGRKMLLayer() */
99 : /************************************************************************/
100 :
101 284 : OGRKMLLayer::~OGRKMLLayer()
102 : {
103 142 : if (nullptr != poFeatureDefn_)
104 142 : poFeatureDefn_->Release();
105 :
106 142 : if (nullptr != poSRS_)
107 84 : poSRS_->Release();
108 :
109 142 : if (nullptr != poCT_)
110 1 : delete poCT_;
111 :
112 142 : CPLFree(pszName_);
113 284 : }
114 :
115 : /************************************************************************/
116 : /* GetLayerDefn() */
117 : /************************************************************************/
118 :
119 2302 : const OGRFeatureDefn *OGRKMLLayer::GetLayerDefn() const
120 : {
121 2302 : return poFeatureDefn_;
122 : }
123 :
124 : /************************************************************************/
125 : /* ResetReading() */
126 : /************************************************************************/
127 :
128 614 : void OGRKMLLayer::ResetReading()
129 : {
130 614 : iNextKMLId_ = 0;
131 614 : nLastAsked = -1;
132 614 : nLastCount = -1;
133 614 : }
134 :
135 : /************************************************************************/
136 : /* GetNextFeature() */
137 : /************************************************************************/
138 :
139 793 : OGRFeature *OGRKMLLayer::GetNextFeature()
140 : {
141 : #ifndef HAVE_EXPAT
142 : return nullptr;
143 : #else
144 : /* -------------------------------------------------------------------- */
145 : /* Loop till we find a feature matching our criteria. */
146 : /* -------------------------------------------------------------------- */
147 793 : KML *poKMLFile = poDS_->GetKMLFile();
148 793 : if (poKMLFile == nullptr)
149 16 : return nullptr;
150 :
151 777 : poKMLFile->selectLayer(nLayerNumber_);
152 :
153 : while (true)
154 : {
155 : auto poFeatureKML = std::unique_ptr<Feature>(
156 960 : poKMLFile->getFeature(iNextKMLId_++, nLastAsked, nLastCount));
157 :
158 960 : if (poFeatureKML == nullptr)
159 207 : return nullptr;
160 :
161 753 : auto poFeature = std::make_unique<OGRFeature>(poFeatureDefn_);
162 :
163 753 : if (poFeatureKML->poGeom)
164 : {
165 753 : poFeature->SetGeometry(std::move(poFeatureKML->poGeom));
166 : }
167 :
168 : // Add fields.
169 1506 : poFeature->SetField(poFeatureDefn_->GetFieldIndex("Name"),
170 753 : poFeatureKML->sName.c_str());
171 1506 : poFeature->SetField(poFeatureDefn_->GetFieldIndex("Description"),
172 753 : poFeatureKML->sDescription.c_str());
173 753 : poFeature->SetFID(iNextKMLId_ - 1);
174 :
175 756 : for (const auto &[key, value] : poFeatureKML->oFields)
176 : {
177 3 : const int nFieldIdx = poFeatureDefn_->GetFieldIndex(key.c_str());
178 3 : if (nFieldIdx > 0)
179 3 : poFeature->SetField(nFieldIdx, value.c_str());
180 : }
181 :
182 753 : if (poFeature->GetGeometryRef() != nullptr && poSRS_ != nullptr)
183 : {
184 753 : poFeature->GetGeometryRef()->assignSpatialReference(poSRS_);
185 : }
186 :
187 : // Check spatial/attribute filters.
188 1715 : if ((m_poFilterGeom == nullptr ||
189 1430 : FilterGeometry(poFeature->GetGeometryRef())) &&
190 677 : (m_poAttrQuery == nullptr ||
191 215 : m_poAttrQuery->Evaluate(poFeature.get())))
192 : {
193 570 : return poFeature.release();
194 : }
195 183 : }
196 :
197 : #endif /* HAVE_EXPAT */
198 : }
199 :
200 : /************************************************************************/
201 : /* GetFeatureCount() */
202 : /************************************************************************/
203 :
204 : #ifndef HAVE_EXPAT
205 : GIntBig OGRKMLLayer::GetFeatureCount(int /* bForce */)
206 : {
207 : return 0;
208 : }
209 : #else
210 :
211 97 : GIntBig OGRKMLLayer::GetFeatureCount(int bForce)
212 : {
213 97 : if (m_poFilterGeom != nullptr || m_poAttrQuery != nullptr)
214 36 : return OGRLayer::GetFeatureCount(bForce);
215 :
216 61 : KML *poKMLFile = poDS_->GetKMLFile();
217 61 : if (nullptr == poKMLFile)
218 0 : return 0;
219 :
220 61 : poKMLFile->selectLayer(nLayerNumber_);
221 :
222 61 : return poKMLFile->getNumFeatures();
223 : }
224 : #endif
225 :
226 : /************************************************************************/
227 : /* WriteSchema() */
228 : /************************************************************************/
229 :
230 40 : CPLString OGRKMLLayer::WriteSchema()
231 : {
232 40 : if (bSchemaWritten_)
233 0 : return "";
234 :
235 80 : CPLString osRet;
236 :
237 40 : const OGRFeatureDefn *featureDefinition = GetLayerDefn();
238 231 : for (int j = 0; j < featureDefinition->GetFieldCount(); j++)
239 : {
240 : const OGRFieldDefn *fieldDefinition =
241 191 : featureDefinition->GetFieldDefn(j);
242 :
243 382 : if (nullptr != poDS_->GetNameField() &&
244 191 : EQUAL(fieldDefinition->GetNameRef(), poDS_->GetNameField()))
245 40 : continue;
246 :
247 302 : if (nullptr != poDS_->GetDescriptionField() &&
248 151 : EQUAL(fieldDefinition->GetNameRef(), poDS_->GetDescriptionField()))
249 40 : continue;
250 :
251 111 : if (osRet.empty())
252 : {
253 : osRet += CPLSPrintf("<Schema name=\"%s\" id=\"%s\">\n", pszName_,
254 37 : pszName_);
255 : }
256 :
257 111 : const char *pszKMLType = nullptr;
258 111 : const char *pszKMLEltName = nullptr;
259 : // Match the OGR type to the GDAL type.
260 111 : switch (fieldDefinition->GetType())
261 : {
262 20 : case OFTInteger:
263 20 : pszKMLType = "int";
264 20 : pszKMLEltName = "SimpleField";
265 20 : break;
266 0 : case OFTIntegerList:
267 0 : pszKMLType = "int";
268 0 : pszKMLEltName = "SimpleArrayField";
269 0 : break;
270 21 : case OFTReal:
271 21 : pszKMLType = "float";
272 21 : pszKMLEltName = "SimpleField";
273 21 : break;
274 0 : case OFTRealList:
275 0 : pszKMLType = "float";
276 0 : pszKMLEltName = "SimpleArrayField";
277 0 : break;
278 38 : case OFTString:
279 38 : pszKMLType = "string";
280 38 : pszKMLEltName = "SimpleField";
281 38 : break;
282 0 : case OFTStringList:
283 0 : pszKMLType = "string";
284 0 : pszKMLEltName = "SimpleArrayField";
285 0 : break;
286 : // TODO: KML doesn't handle these data types yet...
287 32 : case OFTDate:
288 : case OFTTime:
289 : case OFTDateTime:
290 32 : pszKMLType = "string";
291 32 : pszKMLEltName = "SimpleField";
292 32 : break;
293 :
294 0 : default:
295 0 : pszKMLType = "string";
296 0 : pszKMLEltName = "SimpleField";
297 0 : break;
298 : }
299 : osRet += CPLSPrintf("\t<%s name=\"%s\" type=\"%s\"></%s>\n",
300 : pszKMLEltName, fieldDefinition->GetNameRef(),
301 111 : pszKMLType, pszKMLEltName);
302 : }
303 :
304 40 : if (!osRet.empty())
305 37 : osRet += CPLSPrintf("%s", "</Schema>\n");
306 :
307 40 : return osRet;
308 : }
309 :
310 : /************************************************************************/
311 : /* ICreateFeature() */
312 : /************************************************************************/
313 :
314 129 : OGRErr OGRKMLLayer::ICreateFeature(OGRFeature *poFeature)
315 : {
316 129 : CPLAssert(nullptr != poFeature);
317 129 : CPLAssert(nullptr != poDS_);
318 :
319 129 : if (!bWriter_)
320 0 : return OGRERR_FAILURE;
321 :
322 129 : if (bClosedForWriting)
323 : {
324 1 : CPLError(
325 : CE_Failure, CPLE_NotSupported,
326 : "Interleaved feature adding to different layers is not supported");
327 1 : return OGRERR_FAILURE;
328 : }
329 :
330 128 : VSILFILE *fp = poDS_->GetOutputFP();
331 128 : CPLAssert(nullptr != fp);
332 :
333 128 : if (poDS_->GetLayerCount() == 1 && nWroteFeatureCount_ == 0)
334 : {
335 44 : CPLString osRet = WriteSchema();
336 22 : if (!osRet.empty())
337 20 : VSIFPrintfL(fp, "%s", osRet.c_str());
338 22 : bSchemaWritten_ = true;
339 :
340 22 : VSIFPrintfL(fp, "<Folder><name>%s</name>\n", pszName_);
341 : }
342 :
343 128 : ++nWroteFeatureCount_;
344 128 : char *pszEscapedLayerName = OGRGetXML_UTF8_EscapedString(GetDescription());
345 128 : VSIFPrintfL(fp, " <Placemark id=\"%s." CPL_FRMT_GIB "\">\n",
346 : pszEscapedLayerName, nWroteFeatureCount_);
347 128 : CPLFree(pszEscapedLayerName);
348 :
349 128 : if (poFeature->GetFID() == OGRNullFID)
350 128 : poFeature->SetFID(iNextKMLId_++);
351 :
352 : // Find and write the name element
353 128 : if (nullptr != poDS_->GetNameField())
354 : {
355 834 : for (int iField = 0; iField < poFeatureDefn_->GetFieldCount(); iField++)
356 : {
357 706 : OGRFieldDefn *poField = poFeatureDefn_->GetFieldDefn(iField);
358 :
359 1063 : if (poFeature->IsFieldSetAndNotNull(iField) &&
360 357 : EQUAL(poField->GetNameRef(), poDS_->GetNameField()))
361 : {
362 2 : const char *pszRaw = poFeature->GetFieldAsString(iField);
363 2 : while (*pszRaw == ' ')
364 0 : pszRaw++;
365 :
366 2 : char *pszEscaped = OGRGetXML_UTF8_EscapedString(pszRaw);
367 :
368 2 : VSIFPrintfL(fp, "\t<name>%s</name>\n", pszEscaped);
369 2 : CPLFree(pszEscaped);
370 : }
371 : }
372 : }
373 :
374 128 : if (nullptr != poDS_->GetDescriptionField())
375 : {
376 834 : for (int iField = 0; iField < poFeatureDefn_->GetFieldCount(); iField++)
377 : {
378 706 : OGRFieldDefn *poField = poFeatureDefn_->GetFieldDefn(iField);
379 :
380 1063 : if (poFeature->IsFieldSetAndNotNull(iField) &&
381 357 : EQUAL(poField->GetNameRef(), poDS_->GetDescriptionField()))
382 : {
383 1 : const char *pszRaw = poFeature->GetFieldAsString(iField);
384 1 : while (*pszRaw == ' ')
385 0 : pszRaw++;
386 :
387 1 : char *pszEscaped = OGRGetXML_UTF8_EscapedString(pszRaw);
388 :
389 1 : VSIFPrintfL(fp, "\t<description>%s</description>\n",
390 : pszEscaped);
391 1 : CPLFree(pszEscaped);
392 : }
393 : }
394 : }
395 :
396 128 : OGRwkbGeometryType eGeomType = wkbNone;
397 128 : if (poFeature->GetGeometryRef() != nullptr)
398 91 : eGeomType = wkbFlatten(poFeature->GetGeometryRef()->getGeometryType());
399 :
400 128 : if (wkbPolygon == eGeomType || wkbMultiPolygon == eGeomType ||
401 72 : wkbLineString == eGeomType || wkbMultiLineString == eGeomType)
402 : {
403 63 : OGRStylePen *poPen = nullptr;
404 126 : OGRStyleMgr oSM;
405 :
406 63 : if (poFeature->GetStyleString() != nullptr)
407 : {
408 0 : oSM.InitFromFeature(poFeature);
409 :
410 0 : for (int i = 0; i < oSM.GetPartCount(); i++)
411 : {
412 0 : OGRStyleTool *poTool = oSM.GetPart(i);
413 0 : if (poTool && poTool->GetType() == OGRSTCPen)
414 : {
415 0 : poPen = cpl::down_cast<OGRStylePen *>(poTool);
416 0 : break;
417 : }
418 0 : delete poTool;
419 : }
420 : }
421 :
422 63 : VSIFPrintfL(fp, "\t<Style>");
423 63 : if (poPen != nullptr)
424 : {
425 0 : GBool bDefault = FALSE;
426 :
427 : /* Require width to be returned in pixel */
428 0 : poPen->SetUnit(OGRSTUPixel);
429 0 : double fW = poPen->Width(bDefault);
430 0 : if (bDefault)
431 0 : fW = 1;
432 0 : const char *pszColor = poPen->Color(bDefault);
433 0 : const int nColorLen = static_cast<int>(CPLStrnlen(pszColor, 10));
434 0 : if (pszColor != nullptr && pszColor[0] == '#' && !bDefault &&
435 : nColorLen >= 7)
436 : {
437 0 : char acColor[9] = {0};
438 : /* Order of KML color is aabbggrr, whereas OGR color is
439 : * #rrggbb[aa] ! */
440 0 : if (nColorLen == 9)
441 : {
442 0 : acColor[0] = pszColor[7]; /* A */
443 0 : acColor[1] = pszColor[8];
444 : }
445 : else
446 : {
447 0 : acColor[0] = 'F';
448 0 : acColor[1] = 'F';
449 : }
450 0 : acColor[2] = pszColor[5]; /* B */
451 0 : acColor[3] = pszColor[6];
452 0 : acColor[4] = pszColor[3]; /* G */
453 0 : acColor[5] = pszColor[4];
454 0 : acColor[6] = pszColor[1]; /* R */
455 0 : acColor[7] = pszColor[2];
456 0 : VSIFPrintfL(fp, "<LineStyle><color>%s</color>", acColor);
457 0 : VSIFPrintfL(fp, "<width>%g</width>", fW);
458 0 : VSIFPrintfL(fp, "</LineStyle>");
459 : }
460 : else
461 : {
462 0 : VSIFPrintfL(fp,
463 : "<LineStyle><color>ff0000ff</color></LineStyle>");
464 : }
465 : }
466 : else
467 : {
468 63 : VSIFPrintfL(fp, "<LineStyle><color>ff0000ff</color></LineStyle>");
469 : }
470 63 : delete poPen;
471 : // If we're dealing with a polygon, add a line style that will stand out
472 : // a bit.
473 63 : VSIFPrintfL(fp, "<PolyStyle><fill>0</fill></PolyStyle></Style>\n");
474 : }
475 :
476 128 : bool bHasFoundOtherField = false;
477 :
478 : // Write all fields as SchemaData
479 834 : for (int iField = 0; iField < poFeatureDefn_->GetFieldCount(); iField++)
480 : {
481 706 : OGRFieldDefn *poField = poFeatureDefn_->GetFieldDefn(iField);
482 :
483 706 : if (poFeature->IsFieldSetAndNotNull(iField))
484 : {
485 714 : if (nullptr != poDS_->GetNameField() &&
486 357 : EQUAL(poField->GetNameRef(), poDS_->GetNameField()))
487 2 : continue;
488 :
489 710 : if (nullptr != poDS_->GetDescriptionField() &&
490 355 : EQUAL(poField->GetNameRef(), poDS_->GetDescriptionField()))
491 1 : continue;
492 :
493 354 : if (!bHasFoundOtherField)
494 : {
495 86 : VSIFPrintfL(fp,
496 : "\t<ExtendedData><SchemaData schemaUrl=\"#%s\">\n",
497 : pszName_);
498 86 : bHasFoundOtherField = true;
499 : }
500 354 : const char *pszRaw = poFeature->GetFieldAsString(iField);
501 :
502 354 : while (*pszRaw == ' ')
503 0 : pszRaw++;
504 :
505 354 : char *pszEscaped = nullptr;
506 354 : if (poFeatureDefn_->GetFieldDefn(iField)->GetType() == OFTReal)
507 : {
508 152 : pszEscaped = CPLStrdup(pszRaw);
509 : }
510 : else
511 : {
512 202 : pszEscaped = OGRGetXML_UTF8_EscapedString(pszRaw);
513 : }
514 :
515 354 : VSIFPrintfL(fp, "\t\t<SimpleData name=\"%s\">%s</SimpleData>\n",
516 : poField->GetNameRef(), pszEscaped);
517 :
518 354 : CPLFree(pszEscaped);
519 : }
520 : }
521 :
522 128 : if (bHasFoundOtherField)
523 : {
524 86 : VSIFPrintfL(fp, "\t</SchemaData></ExtendedData>\n");
525 : }
526 :
527 : // Write out Geometry - for now it isn't indented properly.
528 128 : if (poFeature->GetGeometryRef() != nullptr)
529 : {
530 91 : char *pszGeometry = nullptr;
531 91 : OGREnvelope sGeomBounds;
532 91 : OGRGeometry *poWGS84Geom = nullptr;
533 :
534 91 : if (nullptr != poCT_)
535 : {
536 1 : poWGS84Geom = poFeature->GetGeometryRef()->clone();
537 1 : poWGS84Geom->transform(poCT_);
538 : }
539 : else
540 : {
541 90 : poWGS84Geom = poFeature->GetGeometryRef();
542 : }
543 :
544 91 : pszGeometry = OGR_G_ExportToKML(OGRGeometry::ToHandle(poWGS84Geom),
545 91 : poDS_->GetAltitudeMode());
546 91 : if (pszGeometry != nullptr)
547 : {
548 91 : VSIFPrintfL(fp, " %s\n", pszGeometry);
549 : }
550 : else
551 : {
552 0 : CPLError(CE_Failure, CPLE_AppDefined,
553 : "Export of geometry to KML failed");
554 : }
555 91 : CPLFree(pszGeometry);
556 :
557 91 : poWGS84Geom->getEnvelope(&sGeomBounds);
558 91 : poDS_->GrowExtents(&sGeomBounds);
559 :
560 91 : if (nullptr != poCT_)
561 : {
562 1 : delete poWGS84Geom;
563 : }
564 : }
565 :
566 128 : VSIFPrintfL(fp, " </Placemark>\n");
567 128 : return OGRERR_NONE;
568 : }
569 :
570 : /************************************************************************/
571 : /* TestCapability() */
572 : /************************************************************************/
573 :
574 345 : int OGRKMLLayer::TestCapability(const char *pszCap) const
575 : {
576 345 : if (EQUAL(pszCap, OLCSequentialWrite))
577 : {
578 22 : return bWriter_;
579 : }
580 323 : else if (EQUAL(pszCap, OLCCreateField))
581 : {
582 28 : return bWriter_ && iNextKMLId_ == 0;
583 : }
584 295 : else if (EQUAL(pszCap, OLCFastFeatureCount))
585 : {
586 : // if( poFClass == NULL
587 : // || m_poFilterGeom != NULL
588 : // || m_poAttrQuery != NULL )
589 0 : return FALSE;
590 :
591 : // return poFClass->GetFeatureCount() != -1;
592 : }
593 :
594 295 : else if (EQUAL(pszCap, OLCStringsAsUTF8))
595 78 : return TRUE;
596 217 : else if (EQUAL(pszCap, OLCZGeometries))
597 21 : return TRUE;
598 :
599 196 : return FALSE;
600 : }
601 :
602 : /************************************************************************/
603 : /* CreateField() */
604 : /************************************************************************/
605 :
606 111 : OGRErr OGRKMLLayer::CreateField(const OGRFieldDefn *poField,
607 : CPL_UNUSED int bApproxOK)
608 : {
609 111 : if (!bWriter_ || iNextKMLId_ != 0)
610 0 : return OGRERR_FAILURE;
611 :
612 111 : OGRFieldDefn oCleanCopy(poField);
613 111 : poFeatureDefn_->AddFieldDefn(&oCleanCopy);
614 :
615 111 : return OGRERR_NONE;
616 : }
617 :
618 : /************************************************************************/
619 : /* SetLayerNumber() */
620 : /************************************************************************/
621 :
622 82 : void OGRKMLLayer::SetLayerNumber(int nLayer)
623 : {
624 82 : nLayerNumber_ = nLayer;
625 82 : }
626 :
627 : /************************************************************************/
628 : /* GetDataset() */
629 : /************************************************************************/
630 :
631 17 : GDALDataset *OGRKMLLayer::GetDataset()
632 : {
633 17 : return poDS_;
634 : }
|