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