root/FalconView/trunk/public/fvw_core/GeodataDataSources/UtilityMethods.cpp @ 2242

Revision 2242, 41.4 KB (checked in by JO94, 6 months ago)

added some comments and a bit more graceful support for deprecated  root:// handling

Line 
1// Copyright (c) 1994-2010 Georgia Tech Research Corporation, Atlanta, GA
2// This file is part of FalconView(tm).
3
4// FalconView(tm) is free software: you can redistribute it and/or modify
5// it under the terms of the GNU Lesser General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// FalconView(tm) is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU Lesser General Public License for more details.
13
14// You should have received a copy of the GNU Lesser General Public License
15// along with FalconView(tm).  If not, see <http://www.gnu.org/licenses/>.
16
17// FalconView(tm) is a trademark of Georgia Tech Research Corporation.
18
19// UtilityMethods.cpp : Implementation of CUtilityMethods
20
21#include "stdafx.h"
22#include "UtilityMethods.h"
23#include "comutil.h"
24#include <string>
25#include "FvOGRPoint.h"
26#include "FvOGRLineString.h"
27#include "FvOGRPolygon.h"
28#include "FvOGRMultiPolygon.h"
29#include "FvOGRMultiPoint.h"
30#include "InternalObjectRegistry.h"
31#include "ComErrorObject.h"
32#include <curl.h>
33#include "FvOGRGeometryCollection.h"
34#include "..\geo_tool\geo_tool.h"
35
36// CUtilityMethods
37
38/*static*/ OGRLocalPointDriver* CUtilityMethods::s_OGRLocalPointDriver = NULL;
39/*static*/ OGRDrawingDriver* CUtilityMethods::s_OGRDrawingDriver = NULL;
40
41/*static*/ std::string CUtilityMethods::GDALErrors;
42
43/*static*/ void CPL_STDCALL CUtilityMethods::GDALErrorHandler(CPLErr eErrClass, int err_no, const char *msg)
44{
45   switch (eErrClass)
46   { // TODO
47   case CE_None:
48      GDALErrors.append("CE_None: ");
49      break;
50   case CE_Debug:
51      GDALErrors.append("CE_Debug: ");
52      break;
53   case CE_Warning:
54      GDALErrors.append("CE_Warning: ");
55      break;
56   case CE_Failure:
57      GDALErrors.append("CE_Failure: ");
58      break;
59   case CE_Fatal:
60      GDALErrors.append("CE_Fatal: ");
61      break;
62   default:
63      GDALErrors.append("Unknown error class: ");
64      break;
65   }
66   //sGDALErrors.append(err_no);
67   GDALErrors.append(msg);
68   //GDALErrors.append("\r\n");
69}
70
71/*static*/ void CUtilityMethods::WriteGDALErrorsToFVErrorLog()
72{
73   WriteStringToFVErrorLog(GDALErrors);
74   GDALErrors = ""; // clear error string
75}
76
77/*static*/ void CUtilityMethods::WriteStringToFVErrorLog(std::string& s)
78{
79   std::wstring ws(s.begin(), s.end());
80   WriteToLogFile(ws.c_str());
81}
82
83/*static*/ void CUtilityMethods::WriteStringToGeodataTraceLog(std::string& s)
84{
85   std::wstring ws(s.begin(), s.end());
86   WriteToLogFile(ws.c_str(), L"GeodataTrace.log");
87}
88
89/*static*/ BOOL CUtilityMethods::s_bGDALInitialized = FALSE;
90
91/*static*/ void CUtilityMethods::WrapGeometry(OGRGeometry* OGR_geometry, IGeometry** geometry)
92{
93   // this method expects that it will own the passed in OGR_geometry because the classes that it wraps it in
94   // will delete the geometry when they clean up
95
96   IGeometryPtr geometry_to_return;
97
98   OGRwkbGeometryType type = OGR_geometry->getGeometryType();
99   switch (type)
100   {
101   case wkbPoint:
102   case wkbPoint25D:
103      {
104         IFvOGRPointPtr Fv_OGR_point = IFvOGRPointPtr(CLSID_FvOGRPoint);
105         CFvOGRPoint* Fv_OGR_point_in_proc = (CFvOGRPoint*)CInternalObjectRegistry::GetObject(Fv_OGR_point->GetHandle());
106         Fv_OGR_point_in_proc->AssociateWithOGRPoint((OGRPoint*)OGR_geometry);
107         geometry_to_return = Fv_OGR_point;
108      }
109      break;
110   case wkbLineString:
111      {
112         IFvOGRLineStringPtr Fv_OGR_line_string = IFvOGRLineStringPtr(CLSID_FvOGRLineString);
113         CFvOGRLineString* Fv_OGR_line_string_in_proc = (CFvOGRLineString*)CInternalObjectRegistry::GetObject(Fv_OGR_line_string->GetHandle());
114         Fv_OGR_line_string_in_proc->AssociateWithOGRLineString((OGRLineString*)OGR_geometry);
115         geometry_to_return = Fv_OGR_line_string;
116      }
117      break;
118   case wkbPolygon:
119      {
120         IFvOGRPolygonPtr Fv_OGR_polygon = IFvOGRPolygonPtr(CLSID_FvOGRPolygon);
121         CFvOGRPolygon* Fv_OGR_polygon_in_proc = (CFvOGRPolygon*)CInternalObjectRegistry::GetObject(Fv_OGR_polygon->GetHandle());
122         Fv_OGR_polygon_in_proc->AssociateWithOGRPolygon((OGRPolygon*)OGR_geometry);
123         geometry_to_return = Fv_OGR_polygon;
124      }
125      break;
126   case wkbMultiPolygon:
127      {
128         IFvOGRMultiPolygonPtr Fv_OGR_multi_polygon = IFvOGRMultiPolygonPtr(CLSID_FvOGRMultiPolygon);
129         CFvOGRMultiPolygon* Fv_OGR_multi_polygon_in_proc = (CFvOGRMultiPolygon*)CInternalObjectRegistry::GetObject(Fv_OGR_multi_polygon->GetHandle());
130         Fv_OGR_multi_polygon_in_proc->AssociateWithOGRMultiPolygon((OGRMultiPolygon*)OGR_geometry);
131         geometry_to_return = Fv_OGR_multi_polygon;
132      }
133      break;
134   case wkbMultiPoint:
135   case wkbMultiPoint25D:
136      {
137         IFvOGRMultiPointPtr Fv_OGR_multi_point = IFvOGRMultiPointPtr(CLSID_FvOGRMultiPoint);
138         CFvOGRMultiPoint* Fv_OGR_multi_point_in_proc = (CFvOGRMultiPoint*)CInternalObjectRegistry::GetObject(Fv_OGR_multi_point->GetHandle());
139         Fv_OGR_multi_point_in_proc->AssociateWithOGRMultiPoint((OGRMultiPoint*)OGR_geometry);
140         geometry_to_return = Fv_OGR_multi_point;
141      }
142      break;
143   case wkbGeometryCollection:
144      {
145         IFvOGRGeometryCollectionPtr Fv_OGR_geometry_collection = IFvOGRGeometryCollectionPtr(CLSID_FvOGRGeometryCollection);
146         CFvOGRGeometryCollection* Fv_OGR_geometry_collection_in_proc = (CFvOGRGeometryCollection*)CInternalObjectRegistry::GetObject(Fv_OGR_geometry_collection->GetHandle());
147         Fv_OGR_geometry_collection_in_proc->AssociateWithOGRGeometryCollection((OGRGeometryCollection*)OGR_geometry);
148         geometry_to_return = Fv_OGR_geometry_collection;
149      }
150      break;
151   default:
152      THROW_ERROR_MSG2(E_FAIL, "unsupported geometry type");
153   }
154
155   *geometry = geometry_to_return.Detach();
156}
157
158//STDMETHODIMP CUtilityMethods::raw_CheckShapefile(BSTR path, VARIANT_BOOL* valid)
159//{
160//   TRY_BLOCK
161//   {
162//      _bstr_t _path = _bstr_t(path);
163//      OGRDataSource* poDS = OGRSFDriverRegistrar::Open((char *)_path, FALSE);
164//      if(poDS == NULL)
165//         return E_FAIL;
166//      OGRSFDriver* driver = poDS->GetDriver();
167//      const char* driver_name = driver->GetName();
168//      *valid = !strcmp(driver_name, "ESRI Shapefile") ? VARIANT_TRUE : VARIANT_FALSE;
169//   }
170//   CATCH_BLOCK_RET
171//
172//   return S_OK;
173//}
174//
175//STDMETHODIMP CUtilityMethods::raw_CopyShapefile(BSTR from, BSTR to)
176//{
177//   // TODO: get .prj file to work (maybe set SR on output?)
178//   return CopyShapefileTo(from, to, _bstr_t("ESRI Shapefile"));
179//}
180//
181//STDMETHODIMP CUtilityMethods::raw_CopyShapefileToKML(BSTR from, BSTR to)
182//{
183//   return CopyShapefileTo(from, to, _bstr_t("KML"));
184//}
185//
186//STDMETHODIMP CUtilityMethods::raw_CopyShapefileTo(BSTR from, BSTR to, BSTR driver)
187//{
188//   return CopyTo(from, to, driver);
189//}
190//
191//STDMETHODIMP CUtilityMethods::raw_CopyKMLTo(BSTR from, BSTR to, BSTR driver)
192//{
193//   return CopyTo(from, to, driver);
194//}
195//
196//STDMETHODIMP CUtilityMethods::raw_CopyTo(BSTR from, BSTR to, BSTR driver)
197//{
198//   TRY_BLOCK
199//   {
200//      std::string _from((char*)_bstr_t(from));
201//      std::string _to((char*)_bstr_t(to));
202//
203//      // open the input Shapefile and layer
204//
205//      OGRDataSource* poDS_in = OGRSFDriverRegistrar::Open(_from.c_str(), FALSE);
206//      if(poDS_in == NULL)
207//         THROW_ERROR_MSG2(E_FAIL, "OGRSFDriverRegistrar::Opentype failed");
208//
209//      OGRLayer* poLayer_in = poDS_in->GetLayer(0);
210//      if(poLayer_in == NULL)
211//         THROW_ERROR_MSG2(E_FAIL, "GetLayer failed");
212//
213//      // create the output Shapefile driver
214//
215//      OGRSFDriver* poDriver_out = OGRSFDriverRegistrar::GetRegistrar()->GetDriverByName((char*)_bstr_t(driver));
216//      if(poDriver_out == NULL)
217//         THROW_ERROR_MSG2(E_FAIL, "GetDriverByName failed");
218//
219//      // create the output datasource
220//
221//      OGRDataSource* poDS_out= poDriver_out->CreateDataSource(_to.c_str(), NULL);
222//      if(poDS_out == NULL)
223//         THROW_ERROR_MSG2(E_FAIL, "CreateDataSource failed");
224//
225//      // create the output layer
226//
227//      size_t layer_name_start = _from.find_last_of("\\", std::string.npos) + 1;
228//      size_t layer_name_end = _from.find_last_of(".", std::string.npos);
229//      std::string layer_name = _from.substr(layer_name_start, layer_name_end - layer_name_start);
230//
231//      OGRLayer* poLayer_out = poDS_out->CreateLayer(layer_name.c_str(), NULL, wkbPoint, NULL); // TODO: support different WKB types
232//      if(poLayer_out == NULL)
233//         THROW_ERROR_MSG2(E_FAIL, "CreateLayer failed");
234//
235//      // create the fields on the output layer
236//
237//      OGRFeatureDefn *poFDefn = poLayer_in->GetLayerDefn();
238//
239//      for(int iField = 0; iField < poFDefn->GetFieldCount(); iField++)
240//      {
241//         OGRFieldDefn* poFieldDefn = poFDefn->GetFieldDefn(iField);
242//         if(poLayer_out->CreateField(poFieldDefn) != OGRERR_NONE)
243//            THROW_ERROR_MSG2(E_FAIL, "CreateField failed");
244//      }
245//
246//      // copy feature by feature
247//
248//      OGRFeature* poFeature;
249//      poLayer_in->ResetReading();
250//
251//      while((poFeature = poLayer_in->GetNextFeature()) != NULL)
252//      {
253//         if(poLayer_out->CreateFeature(poFeature) != OGRERR_NONE)
254//            THROW_ERROR_MSG2(E_FAIL, "CreateFeature failed");
255//         OGRFeature::DestroyFeature(poFeature);
256//      }
257//
258//      // clean up
259//
260//      OGRDataSource::DestroyDataSource(poDS_in);
261//      OGRDataSource::DestroyDataSource(poDS_out);
262//   }
263//   CATCH_BLOCK_RET
264//
265//   return S_OK;
266//}
267//
268//STDMETHODIMP CUtilityMethods::raw_CheckKML(BSTR path, VARIANT_BOOL* valid)
269//{
270//   TRY_BLOCK
271//   {
272//      _bstr_t _path = _bstr_t(path);
273//      OGRDataSource* poDS = OGRSFDriverRegistrar::Open((char *)_path, FALSE);
274//      if(poDS == NULL)
275//         return E_FAIL;
276//      OGRSFDriver* driver = poDS->GetDriver();
277//      const char* driver_name = driver->GetName();
278//      *valid = !strcmp(driver_name, "KML") ? VARIANT_TRUE : VARIANT_FALSE;
279//   }
280//   CATCH_BLOCK_RET
281//
282//   return S_OK;
283//}
284
285//
286// CURL Stuff
287//
288
289// CURLOPT_WRITEFUNCTION:
290// size*nmemb bytes of data are at ptr, stream is user data
291// return must be size*nmemb or curl itself will fail.
292static size_t FetchToString(void* ptr, size_t size, size_t nmemb, void* user) {
293   // static function, only user is DoCurlToString which uses CURLOPT_WRITEDATA
294   // to set the "user" arg which is the C++ string (buffer) to write to.
295   size_t nbytes = size * nmemb;
296   std::string *output_buffer = reinterpret_cast<std::string*>(user);
297   output_buffer->append(reinterpret_cast<char*>(ptr), nbytes);
298   return nbytes;
299}
300
301// Separate worker function to simplify bool return logic.
302static bool DoCurlToString(CURL* curl, const char* url, std::string* data, const char* user_name = NULL, const char* password = NULL)
303{
304   CURLcode code;
305
306   do // break on error
307   {
308      code = curl_easy_setopt(curl, CURLOPT_URL, url);
309      if (code != CURLE_OK)
310         break;
311
312      code = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, FetchToString);
313      if (code != CURLE_OK)
314         break;
315
316      code = curl_easy_setopt(curl, CURLOPT_WRITEDATA, reinterpret_cast<void*>(data));
317      if (code != CURLE_OK)
318         break;
319
320      code = curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
321      if (code != CURLE_OK)
322         break;
323
324      // NOTE: CA not verified for SSL (need DoD keys)
325      // TODO: allow installation of keys, etc...
326      code = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
327      if (code != CURLE_OK)
328         break;
329
330      // set up authentication, if we have a user name and password
331      char* user_name_and_password = NULL;
332      if (user_name && password)
333      {
334         code = curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
335         if (code != CURLE_OK)
336            break;
337
338         size_t size = strlen(user_name) + strlen(password) + 2;
339         user_name_and_password = new char[size];
340         sprintf_s(user_name_and_password, size, "%s:%s", user_name, password);
341
342         code = curl_easy_setopt(curl, CURLOPT_USERPWD, user_name_and_password);
343         if (code != CURLE_OK)
344            break;
345      }
346
347      code = curl_easy_perform(curl);
348      if (user_name_and_password)
349         delete[] user_name_and_password;
350      if (code != CURLE_OK)
351         break;
352
353      return true;
354   }
355   while (false);
356
357   // if we get here, there has been an error
358
359   const size_t BUF_SIZE = 100;
360   char buf[BUF_SIZE];
361   sprintf_s(buf, BUF_SIZE, "%x", code);
362
363   std::string msg("curl error: 0x");
364   msg.append(buf);
365   msg.append(" / ");
366   msg.append(curl_easy_strerror(code)); // leak?? online examples don't delete returned char*
367
368   CUtilityMethods::WriteStringToFVErrorLog(msg);
369
370   return false;
371}
372
373// Wrapper to manage curl handle.  Very simple stateless implementation.  Less
374// simplistic would be to reuse the CURL* handle between invocations.
375bool CurlToString(const char* url, std::string* data, const char* user_name /* = NULL */, const char* password /* = NULL */) {
376   CURL* curl = curl_easy_init();
377   bool ret = DoCurlToString(curl, url, data, user_name, password);
378   curl_easy_cleanup(curl);
379   return ret;
380}
381
382//
383// End of curl stuff
384//
385
386/* static */ std::string CUtilityMethods::ResolveURI(const char* base_url, const char* uri)
387{
388   // call from within a TRY_BLOCK
389
390   // base_url may be a file name, a URL, etc.
391   //  Examples:
392   //    C:\Documents and Settings\User\Desktop\KML Test Data\Screen Overlays.kml
393   //    http://paris.thover.com/images/blog/tdf/2009/tdf2009.kml
394   //    NULL (in which case this just returns uri)
395
396   // uri is an item to fetch from the same location as base_url (if the location is already fully-qualified, it just returns the original location)
397   //  Examples:
398   //    homer-simpson-elvis.gif
399   //    http://paris.thover.com/images/tdf.gif
400
401   // if base_url is NULL, just return uri
402   if (!base_url)
403   {
404      std::string resolved(uri);
405      return resolved;
406   }
407
408   // escape the URL so that it is compatible with ResolveUri (ResolveUri likes some characters and doesn't like others)
409
410   char* escaped = curl_escape(base_url, 0);
411   size_t s = strlen(escaped);
412   char* partially_escaped = new char[s + 1]; // probably extra bytes after buffer because escape sequences converted back to single characters
413   char* f = escaped; // f is the current position in the fully escaped string (reading from)
414   char* p = partially_escaped; // p is the current position in the partially escaped string (writing to)
415
416   while (f <= escaped + s) // <= to copy the terminating null
417   {
418      if (!memcmp(f, "%3A", 3)) // %3A -> :
419      {
420         *(p++) = ':';
421         f += 3;
422      }
423      else if (!memcmp(f, "%5C", 3)) // %5C -> /  (note that 0x5c is really a backslash)
424      {
425         *(p++) = '/';
426         f += 3;
427      }
428      else if (!memcmp(f, "%2E", 3)) // %2E -> .
429      {
430         *(p++) = '.';
431         f += 3;
432      }
433      else if (!memcmp(f, "%2F", 3)) // %2F -> /
434      {
435         *(p++) = '/';
436         f += 3;
437      }
438      else // copy as-is
439         *(p++) = *(f++);
440   }
441
442   curl_free(escaped);
443
444   // resolve the full URL of the image file name from the base URL and the image file name (identifier)
445   std::string result;
446   if (!kmlengine::ResolveUri(partially_escaped, uri, &result))
447      result = uri; // failures may happen on fully-qualified local paths, which are okay as they are
448   delete[] partially_escaped;
449   char* unescaped = curl_unescape(result.c_str(), 0); // at this point, unescaped holds the resolved URL to the image
450   std::string resolved(unescaped);
451   curl_free(unescaped);
452   return resolved;
453}
454
455/* static */
456void CUtilityMethods::Create32BppBitmap(VARIANT* imageData, int x_size, int y_size)
457{
458   // initialize the return array
459   SAFEARRAYBOUND saBound;
460   saBound.cElements = 40 + 4*x_size*y_size; // 40 bytes for the header plus 32 bits per image pixel
461   saBound.lLbound = 0;
462   VariantInit(imageData);
463   imageData->vt = VT_ARRAY | VT_I1;
464   imageData->parray = SafeArrayCreate(VT_I1, 1, &saBound);
465   UCHAR* pos = NULL;
466   SafeArrayAccessData(imageData->parray, reinterpret_cast<void **>(&pos));
467
468   // populate the bitmap header
469   pos[4] = x_size % 256; // 4 & 5 hold width
470   pos[5] = (int)(x_size / 256);
471   pos[8] = y_size % 256; // 8 & 9 hold height
472   pos[9] = (int)(y_size / 256);
473   pos[14] = 32; // 32 bits per pixel (BGRA)
474
475   SafeArrayUnaccessData(imageData->parray);
476}
477
478/* static */ void CUtilityMethods::ReplaceAllInString(std::string& subject, const char* find, const char* replacement)
479{
480   if (!strcmp(find, replacement))
481      return;
482   size_t find_length = strlen(find);
483   std::string::size_type position = subject.find(find);
484   while (position != subject.npos)
485   {
486      subject.replace(position, find_length, replacement);
487      position = subject.find(find);
488   }
489}
490
491/* static */ bool CUtilityMethods::LooksLikeHttp(const char* s, bool https /* = false */)
492{
493   return strlen(s) > (https ? (size_t)8 : (size_t)7) // longer than "http(s)://"
494      && ::tolower(s[0]) == 'h'
495      && ::tolower(s[1]) == 't'
496      && ::tolower(s[2]) == 't'
497      && ::tolower(s[3]) == 'p'
498      && (!https || ::tolower(s[4]) == 's');
499}
500
501/* static */ bool CUtilityMethods::LooksLikeRoot(const char* s)
502{
503   return strlen(s) > 5 // longer than "root:"
504      && ::tolower(s[0]) == 'r'
505      && ::tolower(s[1]) == 'o'
506      && ::tolower(s[2]) == 'o'
507      && ::tolower(s[3]) == 't'
508      && s[4] == ':';
509}
510
511STDMETHODIMP CUtilityMethods::raw_GetLeakData(VARIANT* leakData)
512{
513   VariantInit(leakData);
514   leakData->vt = VT_UINT;
515   leakData->uintVal = (UINT)CInternalObjectRegistry::ObjectCount();
516   return S_OK;
517}
518
519/*static*/ BOOL CUtilityMethods::FeatureInFilter(IFeature* feature, IFilter* filter, std::string& error_message)
520{
521   if (filter == NULL)
522      return TRUE; // NULL filters mean no filtering at all
523
524   // we currently only support geometry filters
525   IGeometryFilterPtr geometryFilter = filter;
526   if (!geometryFilter)
527   {
528      error_message = "unsupported filter type - currently only support geometry filters";
529      return FALSE;
530   }
531
532   // the filter's geometry must support spatial relations
533   ISpatialRelationPtr spatialRelation = geometryFilter->Geometry;
534   if (!spatialRelation)
535   {
536      error_message = "filter geometry must support spatial relations";
537      return FALSE;
538   }
539
540   return spatialRelation->Intersects(feature->Geometry);
541}
542
543/*static*/ void CUtilityMethods::GetRectangularFilterBounds(IFilter* filter, double* minX, double* minY, double* maxX, double* maxY)
544{
545   // This should be called from within a TRY_BLOCK.  Filter must be a rectangular filter that meets the criteria below.
546
547   IGeometryFilterPtr geometryFilter = filter;
548   if (!geometryFilter)
549      THROW_ERROR_MSG2(E_FAIL, "filter must be geometry filter");
550
551   IPolygonPtr polygon = geometryFilter->Geometry;
552   if (!polygon)
553      THROW_ERROR_MSG2(E_FAIL, "filter geometry must be a polygon");
554
555   if (polygon->NumInteriorRings != 0)
556      THROW_ERROR_MSG2(E_FAIL, "filter polygon must have zero interior rings");
557
558   ILinearRingPtr exteriorRing = polygon->ExteriorRing();
559   if (exteriorRing->NumPoints != 5)//closed rings with four corners have the initial point repeated (5 points)
560      THROW_ERROR_MSG2(E_FAIL, "filter polygon must have four corners");
561   if (!exteriorRing->IsClosed)
562      THROW_ERROR_MSG2(E_FAIL, "filter polygon must be closed");
563
564   geometryFilter->Extent2D(minX, minY, maxX, maxY);
565
566   if (*minX > *maxX || *minY > *maxY)
567   {
568      // date line crossings should be handled by adjusting bounds by 360
569      THROW_ERROR_MSG2(E_FAIL, "filter maximums and minimums must be proper");
570   }
571
572   for (int index = 0; index < 4; index++)
573   {
574      // ensure that we are dealing with a rectangle
575      IPointPtr point = exteriorRing->Point(index);
576      if ( (point->x != *minX && point->x != *maxX) || (point->y != *minY && point->y != *maxY) )
577         THROW_ERROR_MSG2(E_FAIL, "filter polygon must be a rectangle");
578   }
579
580   if(*minX == *maxX)
581   {
582      //we wrap around the world
583      *minX = -180;
584      *maxX = 180;
585   }
586}
587
588/* static */ void CUtilityMethods::ShiftXWindowWithinM180To360(double* minX, double* maxX)
589{
590   // bring the min x bounds within -180 to 180
591   while (*minX < -180.0)
592      *minX += 360.0;
593   while (*minX > 180.0)
594      *minX -= 360.0;
595
596   // bring the max x bounds within -180 to 360 (360 allows date line crossing)
597   while (*maxX < -180.0)
598      *maxX += 360.0;
599   while (*maxX > 360.0)
600      *maxX -= 360.0;
601
602   // make sure that max >= min
603   while (*minX > *maxX)
604      *maxX += 360.0;
605}
606
607/* static */ bool CUtilityMethods::RectanglesInRectangle(
608   DOUBLE minX, DOUBLE minY, DOUBLE maxX, DOUBLE maxY,
609   DOUBLE leftLon, DOUBLE bottomLat, DOUBLE rightLon, DOUBLE topLat)
610{
611   return GEO_intersect(minY, minX, maxY, maxX,
612                        bottomLat, leftLon, topLat, rightLon) == TRUE;
613}
614
615/* static */ bool CUtilityMethods::KMLFeatureInRectangle(
616   kmldom::FeaturePtr feature, DOUBLE leftLon, DOUBLE bottomLat, DOUBLE rightLon, DOUBLE topLat)
617{
618   kmlengine::Bbox bbox;
619
620   if (kmlengine::GetFeatureBounds(feature, &bbox)) // features with no defined bbox are included
621   {
622      double minX = bbox.get_west();
623      double minY = bbox.get_south();
624      double maxX = bbox.get_east();
625      double maxY = bbox.get_north();
626
627      while (rightLon > 180.0)
628         rightLon -= 180.0;
629
630      return RectanglesInRectangle(minX, minY, maxX, maxY, leftLon, bottomLat, rightLon, topLat);
631   }
632
633   return true;
634}
635
636/* static */ kmldom::ElementPtr CUtilityMethods::GetAncestorOfType(const kmldom::ElementPtr& element, kmldom::KmlDomType type)
637{
638   kmldom::ElementPtr ret = element->GetParent();
639   while(ret != NULL && !ret->IsA(type))
640      ret = ret->GetParent();
641
642   return ret;
643}
644
645/* static */ bool CUtilityMethods::KMLFeatureInRegion(kmldom::FeaturePtr feature,
646   DOUBLE leftLon, DOUBLE bottomLat, DOUBLE rightLon, DOUBLE topLat,
647   DOUBLE degreesPerPixelX, DOUBLE degreesPerPixelY)  // use negative number if dpp is not set)
648{
649   // If the given feature does not have a region, then find the youngest
650   // ancestor that does
651   while (feature != NULL && feature->has_region() == false)
652      feature = kmldom::AsFeature(GetAncestorOfType(feature, kmldom::Type_Feature));
653
654   if (feature != NULL && feature->has_region())
655   {
656      kmldom::RegionPtr region = feature->get_region();
657
658      if (region->has_latlonaltbox()) // required per spec, but we'll be nice...
659      {
660         // return false if the <LatLonAltBox> is not in view
661
662         kmldom::LatLonAltBoxPtr bbox = region->get_latlonaltbox();
663
664         double minX = bbox->get_west();
665         double minY = bbox->get_south();
666         double maxX = bbox->get_east();
667         double maxY = bbox->get_north();
668
669         if (!RectanglesInRectangle(minX, minY, maxX, maxY, leftLon, bottomLat, rightLon, topLat))
670            return false;
671
672         // return false if the <Lod> requirements aren't met
673
674         if (region->has_lod())
675         {
676            kmldom::LodPtr lod = region->get_lod();
677
678            // compute the square root of the area, in square pixels, of the
679            // region projected onto the surface
680            double regionAreaPixels = sqrt(((maxX - minX) / degreesPerPixelX)
681               * ((maxY - minY) / degreesPerPixelY));
682
683            // test <minLodPixels>
684            if (lod->has_minlodpixels())
685            {
686               double minLodPixels = lod->get_minlodpixels();
687               if (regionAreaPixels < minLodPixels)
688                  return false;
689            }
690
691            // test <maxLodPixels>
692            if (lod->has_maxlodpixels())
693            {
694               double maxLodPixels = lod->get_maxlodpixels();
695               if (maxLodPixels > 0) // -1 means active to infinite size on screen
696               {
697                  if (regionAreaPixels > maxLodPixels)
698                     return false;
699               }
700            }
701         }
702      }
703   }
704
705   return true; // returns true if no region, or region is active
706}
707
708
709//
710// Geometry copy functions (OGR)
711//
712
713/*static*/ OGRPoint* CUtilityMethods::Copy2OGR(IPoint* point)
714{
715   OGRPoint *ogrPoint = new OGRPoint;
716   ogrPoint->setX(point->x);
717   ogrPoint->setY(point->y);
718   return ogrPoint;
719}
720
721/*static*/ OGRLineString* CUtilityMethods::Copy2OGR(ILineString* lineString)
722{
723   OGRLineString *ogrLineString = new OGRLineString;
724   for( long i = 0 ; i < lineString->GetNumPoints() ; i++)
725   {
726      OGRPoint *geo = Copy2OGR(lineString->Point(i));
727      ogrLineString->addPoint(geo);
728      delete geo;
729   }
730   return ogrLineString;
731}
732
733/*static*/ OGRLinearRing* CUtilityMethods::Copy2OGR(ILinearRing* linearRing)
734{
735   OGRLinearRing *ogrLinearRing = new OGRLinearRing;
736   for( long i = 0 ; i < linearRing->GetNumPoints() ; i++)
737   {
738      OGRPoint *geo = Copy2OGR(linearRing->Point(i));
739      ogrLinearRing->addPoint(geo);
740      delete geo;
741   }
742   return ogrLinearRing;
743}
744
745/*static*/ OGRPolygon* CUtilityMethods::Copy2OGR(IPolygon* polygon)
746{
747   OGRPolygon *ogrPolygon = new OGRPolygon;
748
749   OGRLinearRing *geo = Copy2OGR(polygon->ExteriorRing());
750   ogrPolygon->addRing(geo);
751   delete geo;
752
753   for( long i = 0 ; i < polygon->GetNumInteriorRings() ; i++)
754   {
755      geo = Copy2OGR(polygon->InteriorRing(i));
756      ogrPolygon->addRing(geo);
757      delete geo;
758   }
759
760   return ogrPolygon;
761}
762
763/*static*/ OGRGeometryCollection* CUtilityMethods::Copy2OGR(IGeometryCollection* collection)
764{
765   OGRGeometryCollection *ogrCollection = new OGRGeometryCollection;
766
767   for( long i = 0 ; i < collection->GetNumGeometries() ; i ++)
768   {
769      OGRGeometry* geo = CopyGeometry2OGR(collection->Geometry(i));
770      ogrCollection->addGeometry(geo);
771      delete geo;
772   }
773   return ogrCollection;
774}
775
776/*static*/ OGRGeometry* CUtilityMethods::CopyGeometry2OGR(IGeometry* featureGeometry)
777{
778   //we check for type and call the overloaded Copy methods
779
780   IPointPtr point = featureGeometry;
781   if (point)
782      return Copy2OGR(point);
783
784   ILineStringPtr lineString = featureGeometry;
785   if (lineString)
786      return Copy2OGR(lineString);
787
788   ILinearRingPtr linearRing = featureGeometry;
789   if (linearRing)
790      return Copy2OGR(linearRing);
791
792   IPolygonPtr polygon = featureGeometry;
793   if (polygon)
794      return Copy2OGR(polygon);
795
796   IGeometryCollectionPtr geometryCollection = featureGeometry;
797   if (geometryCollection)
798      return Copy2OGR(geometryCollection);
799
800   THROW_ERROR_MSG2(E_FAIL, "unsupported geometry type");
801}
802
803//
804// End of Geometry copy functions (OGR)
805//
806
807/* static */ bool CUtilityMethods::ParseKMLDateTime(const std::string& sDateTime, COleDateTime& dtOut, std::string& error) // returns UTC
808{
809   //
810   // From http://code.google.com/apis/kml/documentation/kmlreference.html#timespan
811   //
812   // Specifies a single moment in time. The value is a dateTime, which can be one of the following:
813   // dateTime gives second resolution
814   // date gives day resolution
815   // gYearMonth gives month resolution
816   // gYear gives year resolution
817   // The following examples show different resolutions for the <when> value:
818   // gYear (YYYY)
819   // <TimeStamp>
820   //   <when>1997</when>
821   // </TimeStamp>
822   // gYearMonth (YYYY-MM)
823   // <TimeStamp>
824   //   <when>1997-07</when>
825   // </TimeStamp>
826   // date (YYYY-MM-DD)
827   // <TimeStamp>
828   //   <when>1997-07-16</when>
829   // </TimeStamp>
830   // dateTime (YYYY-MM-DDThh:mm:ssZ)
831   // Here, T is the separator between the calendar and the hourly notation of time, and Z indicates UTC. (Seconds are required.)
832   // <TimeStamp>
833   //   <when>1997-07-16T07:30:15Z</when>
834   // </TimeStamp>
835   // dateTime (YYYY-MM-DDThh:mm:sszzzzzz)
836   // This example gives the local time and then the ± conversion to UTC.
837   // <TimeStamp>
838   //   <when>1997-07-16T10:30:15+03:00</when>
839   // </TimeStamp>
840   //
841
842   size_t len = sDateTime.length();
843
844   if (len < 4)
845   {
846      error = "datetime length too short";
847      return false;
848   }
849
850   // parse year
851   std::string sYear = sDateTime.substr(0, 4);
852   int year = atoi(sYear.c_str());
853   if (year < 100 || year > 9999) // http://msdn.microsoft.com/en-us/library/38wh24td.aspx
854   {
855      error = "invalid year";
856      return false;
857   }
858
859   // parse month
860   int month = 1;
861   if (len > 4)
862   {
863      if (len < 7)
864      {
865         error = "datetime length should not be 5 or 6";
866         return false;
867      }
868
869      std::string sMonth = sDateTime.substr(5, 2);
870      month = atoi(sMonth.c_str());
871      if (month < 1 || month > 12)
872      {
873         error = "invalid month";
874         return false;
875      }
876   }
877
878   // parse day
879   int day = 1;
880   if (len > 7)
881   {
882      if (len < 10)
883      {
884         error = "datetime length should not be 8 or 9";
885         return false;
886      }
887
888      std::string sDay = sDateTime.substr(8, 2);
889      day = atoi(sDay.c_str());
890      if (day < 1 || day > 31)
891      {
892         error = "invalid day";
893         return false;
894      }
895   }
896
897   // parse hour, minute, second and time zone offset
898   int hour = 0;
899   int minute = 0;
900   int second = 0;
901   if (len > 10)
902   {
903      if (len != 19 && len != 20 && len != 25) // we allow a length of 19 to allow for times that omit the Z for Zulu
904      {
905         error = "invalid datetime";
906         return false;
907      }
908
909      std::string sHour = sDateTime.substr(11, 2);
910      hour = atoi(sHour.c_str());
911      if (hour < 0 || hour > 23)
912      {
913         error = "invalid hour";
914         return false;
915      }
916
917      std::string sMinute = sDateTime.substr(14, 2);
918      minute = atoi(sMinute.c_str());
919      if (minute < 0 || minute > 59)
920      {
921         error = "invalid minute";
922         return false;
923      }
924
925      std::string sSecond = sDateTime.substr(17, 2);
926      second = atoi(sSecond.c_str());
927      if (second < 0 || second > 59)
928      {
929         error = "invalid second";
930         return false;
931      }
932
933      if (len == 25) // zone offset, not Zulu
934      {
935         std::string sOffsetHours = sDateTime.substr(20, 2);
936         int offsetHours = atoi(sOffsetHours.c_str());
937         if (offsetHours < 0 || offsetHours > 13)
938         {
939            error = "invalid offset hours";
940            return false;
941         }
942
943         std::string sOffsetMinutes = sDateTime.substr(22, 2);
944         int offsetMinutes = atoi(sOffsetMinutes.c_str());
945         if (offsetMinutes < 0 || offsetMinutes > 59)
946         {
947            error = "invalid offset minutes";
948            return false;
949         }
950
951         // apply offset and return
952         COleDateTimeSpan offset(0, offsetHours, offsetMinutes, 0);
953         dtOut.SetDateTime(year, month, day, hour, minute, second);
954         if (sDateTime[19] == '-')
955            dtOut -= offset;
956         else
957            dtOut += offset;
958         return true;
959      }
960   }
961
962   dtOut.SetDateTime(year, month, day, hour, minute, second); // UTC
963   return true;
964}
965
966/* static */ bool CUtilityMethods::KMLTimePrimitiveInTemporalFilter(kmldom::TimePrimitivePtr time_primitive, ITemporalFilter* temporal_filter)
967{
968   // call within TRY_BLOCK for error handling purposes
969
970   // get the begining and ending as strings
971
972   std::string sBegin, sEnd; // "" indicates unbounded
973
974   kmldom::TimeSpanPtr time_span = kmldom::AsTimeSpan(time_primitive);
975   if (time_span)
976   {
977      if (time_span->has_begin())
978         sBegin = time_span->get_begin();
979      if (time_span->has_end())
980         sEnd = time_span->get_end();
981   }
982
983   kmldom::TimeStampPtr time_stamp = kmldom::AsTimeStamp(time_primitive);
984   if (time_stamp)
985   {
986      if (time_stamp->has_when())
987         sBegin = sEnd = time_stamp->get_when();
988   }
989
990   // return false if we are before the begin date
991   std::string error;
992   if (sBegin.length() > 0)
993   {
994      COleDateTime dtBegin;
995      if (!ParseKMLDateTime(sBegin, dtBegin, error))
996      {
997         std::string msg("Could not parse time in KML (");
998         msg.append(error);
999         msg.append("): ");
1000         msg.append(sBegin);
1001         const size_t MAX_LENGTH = 250;
1002         if (msg.size() > MAX_LENGTH)
1003            msg = msg.substr(0, MAX_LENGTH);
1004         THROW_ERROR_MSG2(E_FAIL, msg.c_str());
1005      }
1006
1007      COleDateTime filterEnd(temporal_filter->End);
1008      if (filterEnd < dtBegin)
1009         return false;
1010   }
1011
1012   // return false if we are after the end date
1013   if (temporal_filter->Start == temporal_filter->End && time_stamp)
1014   {
1015      // SPECIAL CASE: If we have a KML time stamp and the temporal filter is a single moment in time, we ignore the end date, which
1016      // essentially makes us capture any event before the end date.
1017   }
1018   else
1019   {
1020      if (sEnd.length() > 0)
1021      {
1022         COleDateTime dtEnd;
1023         if (!ParseKMLDateTime(sEnd, dtEnd, error))
1024         {
1025            std::string msg("Could not parse time in KML (");
1026            msg.append(error);
1027            msg.append("): ");
1028            msg.append(sEnd);
1029            const size_t MAX_LENGTH = 250;
1030            if (msg.size() > MAX_LENGTH)
1031               msg = msg.substr(0, MAX_LENGTH);
1032            THROW_ERROR_MSG2(E_FAIL, msg.c_str());
1033         }
1034
1035         COleDateTime filterStart(temporal_filter->Start);
1036         if (filterStart > dtEnd)
1037            return false;
1038      }
1039   }
1040
1041   return true; // time spans intersect
1042}
1043
1044/* static */ bool CUtilityMethods::KMLTimePrimitiveHasStart(kmldom::TimePrimitivePtr time_primitive)
1045{
1046   kmldom::TimeSpanPtr time_span = kmldom::AsTimeSpan(time_primitive);
1047   if (time_span)
1048      return time_span->has_begin();
1049
1050   kmldom::TimeStampPtr time_stamp = kmldom::AsTimeStamp(time_primitive);
1051   if (time_stamp)
1052      return time_stamp->has_when();
1053
1054   return false;
1055}
1056
1057/* static */ DATE CUtilityMethods::KMLTimePrimitiveStart(kmldom::TimePrimitivePtr time_primitive)
1058{
1059   // call from within a TRY_BLOCK
1060
1061   kmldom::TimeSpanPtr time_span = kmldom::AsTimeSpan(time_primitive);
1062   if (time_span)
1063   {
1064      if (time_span->has_begin())
1065      {
1066         COleDateTime dtStart;
1067         std::string error;
1068
1069         if (!CUtilityMethods::ParseKMLDateTime(time_span->get_begin(), dtStart, error))
1070         {
1071            const size_t MAX_LENGTH = 250;
1072            if (error.size() > MAX_LENGTH)
1073               error = error.substr(0, MAX_LENGTH);
1074            THROW_ERROR_MSG2(E_FAIL, error.c_str());
1075         }
1076
1077         return (DATE)dtStart;
1078      }
1079   }
1080
1081   kmldom::TimeStampPtr time_stamp = kmldom::AsTimeStamp(time_primitive);
1082   if (time_stamp)
1083   {
1084      if (time_stamp->has_when())
1085      {
1086         COleDateTime dtStart;
1087         std::string error;
1088
1089         if (!CUtilityMethods::ParseKMLDateTime(time_stamp->get_when(), dtStart, error))
1090         {
1091            const size_t MAX_LENGTH = 250;
1092            if (error.size() > MAX_LENGTH)
1093               error = error.substr(0, MAX_LENGTH);
1094            THROW_ERROR_MSG2(E_FAIL, error.c_str());
1095         }
1096
1097         return (DATE)dtStart;
1098      }
1099   }
1100
1101   THROW_ERROR_MSG2(E_FAIL, "could not find a start time");
1102}
1103
1104/* static */ bool CUtilityMethods::KMLTimePrimitiveHasEnd(kmldom::TimePrimitivePtr time_primitive)
1105{
1106   kmldom::TimeSpanPtr time_span = kmldom::AsTimeSpan(time_primitive);
1107   if (time_span)
1108      return time_span->has_end();
1109
1110   kmldom::TimeStampPtr time_stamp = kmldom::AsTimeStamp(time_primitive);
1111   if (time_stamp)
1112      return time_stamp->has_when();
1113
1114   return false;
1115}
1116
1117/* static */ DATE CUtilityMethods::KMLTimePrimitiveEnd(kmldom::TimePrimitivePtr time_primitive)
1118{
1119   // call from within a TRY_BLOCK
1120
1121   kmldom::TimeSpanPtr time_span = kmldom::AsTimeSpan(time_primitive);
1122   if (time_span)
1123   {
1124      if (time_span->has_end())
1125      {
1126         COleDateTime dtEnd;
1127         std::string error;
1128
1129         if (!CUtilityMethods::ParseKMLDateTime(time_span->get_end(), dtEnd, error))
1130         {
1131            const size_t MAX_LENGTH = 250;
1132            if (error.size() > MAX_LENGTH)
1133               error = error.substr(0, MAX_LENGTH);
1134            THROW_ERROR_MSG2(E_FAIL, error.c_str());
1135         }
1136
1137         return (DATE)dtEnd;
1138      }
1139   }
1140
1141   kmldom::TimeStampPtr time_stamp = kmldom::AsTimeStamp(time_primitive);
1142   if (time_stamp)
1143   {
1144      if (time_stamp->has_when())
1145      {
1146         COleDateTime dtEnd;
1147         std::string error;
1148
1149         if (!CUtilityMethods::ParseKMLDateTime(time_stamp->get_when(), dtEnd, error))
1150         {
1151            const size_t MAX_LENGTH = 250;
1152            if (error.size() > MAX_LENGTH)
1153               error = error.substr(0, MAX_LENGTH);
1154            THROW_ERROR_MSG2(E_FAIL, error.c_str());
1155         }
1156
1157         return (DATE)dtEnd;
1158      }
1159   }
1160
1161   THROW_ERROR_MSG2(E_FAIL, "could not find an end time");
1162}
1163
1164// methods to create WKT from geometries
1165
1166/* static */ void CUtilityMethods::PointToWKT(IPoint* point, std::string& wkt)
1167{
1168   wkt = "Point(";
1169   AppendDoubleTupleToString(point->x, point->y, wkt);
1170   wkt.append(")");
1171}
1172
1173/* static */ void CUtilityMethods::MultiPointToWKT(IGeometryCollection* multi_point, std::string& wkt)
1174{
1175   wkt = "MultiPoint(";
1176   long numGeometries = multi_point->NumGeometries;
1177   for (long i = 0; i < numGeometries; i++)
1178   {
1179      wkt.append("(");
1180      IPointPtr point = multi_point->Geometry(i);
1181      AppendDoubleTupleToString(point->x, point->y, wkt);
1182      if (i < numGeometries - 1)
1183         wkt.append("), ");
1184      else
1185         wkt.append("))");
1186   }
1187}
1188
1189/* static */ void CUtilityMethods::LineStringToWKT(ILineString* line_string, std::string& wkt)
1190{
1191   wkt = "LineString";
1192   AppendLineStringToString(line_string, wkt);
1193}
1194
1195/* static */ void CUtilityMethods::LinearRingToWKT(ILinearRing* linear_ring, std::string& wkt)
1196{
1197   wkt = "LinearRing"; // non-standard, but not uncommon
1198   AppendLineStringToString(linear_ring, wkt);
1199}
1200
1201/* static */ void CUtilityMethods::PolygonToWKT(IPolygon* polygon, std::string& wkt)
1202{
1203   wkt = "Polygon";
1204   AppendPolygonToString(polygon, wkt);
1205}
1206
1207/* static */ void CUtilityMethods::MultiPolygonToWKT(IGeometryCollection* multi_polygon, std::string& wkt)
1208{
1209   wkt = "MultiPolygon(";
1210   long numGeometries = multi_polygon->NumGeometries;
1211   for (long i = 0; i < numGeometries; i++)
1212   {
1213      IPolygonPtr polygon = multi_polygon->Geometry(i);
1214      AppendPolygonToString(polygon, wkt);
1215      if (i < numGeometries - 1)
1216         wkt.append(", ");
1217   }
1218   wkt.append(")");
1219}
1220
1221/* static */ void CUtilityMethods::GeometryCollectionToWKT(IGeometryCollection* geometry_collection, std::string& wkt)
1222{
1223   wkt = "GeometryCollection(";
1224   long numGeometries = geometry_collection->NumGeometries;
1225   for (long i = 0; i < numGeometries; i++)
1226   {
1227      if (i > 0)
1228         wkt.append(", ");
1229
1230      IGeometryPtr geometry = geometry_collection->Geometry(i);
1231     
1232      IPointPtr point = geometry;
1233      if (point)
1234      {
1235         PointToWKT(point, wkt);
1236         continue;
1237      }
1238
1239      ILinearRingPtr linear_ring = geometry;
1240      if (linear_ring) // must test before line string
1241      {
1242         LinearRingToWKT(linear_ring, wkt);
1243         continue;
1244      }
1245
1246      ILineStringPtr line_string = geometry;
1247      if (line_string)
1248      {
1249         LineStringToWKT(line_string, wkt);
1250         continue;
1251      }
1252
1253      IPolygonPtr polygon = geometry;
1254      if (polygon)
1255      {
1256         PolygonToWKT(polygon, wkt);
1257         continue;
1258      }
1259
1260      // Note the behavior here: this only identifies multi-point and multi-polygon if they are OGR
1261
1262      IGeometryCollectionPtr geometry_collection = geometry;
1263
1264      IFvOGRMultiPointPtr multi_point;
1265      if (multi_point) // must test before geometry collection
1266      {
1267         MultiPointToWKT(geometry_collection, wkt);
1268         continue;
1269      }
1270
1271      IFvOGRMultiPolygonPtr multi_polygon;
1272      if (multi_point) // must test before geometry collection
1273      {
1274         MultiPolygonToWKT(geometry_collection, wkt);
1275         continue;
1276      }
1277
1278      if (geometry_collection)
1279      {
1280         GeometryCollectionToWKT(geometry_collection, wkt);
1281         continue;
1282      }
1283
1284      wkt.append("UNKNOWN_GEOMETRY");
1285   }
1286   wkt.append(")");
1287}
1288
1289/* static */ void CUtilityMethods::AppendDoubleToString(double d, std::string& s)
1290{
1291   const int BUF_SIZE = 30;
1292   char buf[BUF_SIZE];
1293   sprintf_s(buf, BUF_SIZE, "%g", d);
1294   s.append(buf);
1295}
1296
1297/* static */ void CUtilityMethods::AppendDoubleTupleToString(double d1, double d2, std::string& s)
1298{
1299   AppendDoubleToString(d1, s);
1300   s.append(" ");
1301   AppendDoubleToString(d2, s);
1302}
1303
1304/* static */ void CUtilityMethods::AppendLineStringToString(ILineString* line_string, std::string& s)
1305{
1306   s.append("(");
1307   long numPoints = line_string->NumPoints;
1308   for (long i = 0; i < numPoints; i++)
1309   {
1310      IPointPtr point = line_string->Point(i);
1311      AppendDoubleTupleToString(point->x, point->y, s);
1312      if (i < numPoints - 1)
1313         s.append(", ");
1314   }
1315   s.append(")");
1316}
1317
1318/* static */ void CUtilityMethods::AppendPolygonToString(IPolygon* polygon, std::string& s)
1319{
1320   s.append("(");
1321   AppendLineStringToString(polygon->ExteriorRing(), s);
1322   long numInteriorRings = polygon->NumInteriorRings;
1323   for (long i = 0; i < numInteriorRings; i++)
1324   {
1325      s.append(", ");
1326      AppendLineStringToString(polygon->InteriorRing(i), s);
1327   }
1328   s.append(")");
1329}
Note: See TracBrowser for help on using the browser.