Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/src/utils/geom/GeoConvHelper.cpp
169678 views
1
/****************************************************************************/
2
// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3
// Copyright (C) 2001-2025 German Aerospace Center (DLR) and others.
4
// This program and the accompanying materials are made available under the
5
// terms of the Eclipse Public License 2.0 which is available at
6
// https://www.eclipse.org/legal/epl-2.0/
7
// This Source Code may also be made available under the following Secondary
8
// Licenses when the conditions for such availability set forth in the Eclipse
9
// Public License 2.0 are satisfied: GNU General Public License, version 2
10
// or later which is available at
11
// https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13
/****************************************************************************/
14
/// @file GeoConvHelper.cpp
15
/// @author Daniel Krajzewicz
16
/// @author Jakob Erdmann
17
/// @author Michael Behrisch
18
/// @date 2006-08-01
19
///
20
// static methods for processing the coordinates conversion for the current net
21
/****************************************************************************/
22
#include <config.h>
23
24
#include <map>
25
#include <cmath>
26
#include <cassert>
27
#include <climits>
28
#include <regex>
29
#include <utils/common/MsgHandler.h>
30
#include <utils/common/ToString.h>
31
#include <utils/geom/GeomHelper.h>
32
#include <utils/options/OptionsCont.h>
33
#include <utils/iodevices/OutputDevice.h>
34
#include "GeoConvHelper.h"
35
36
37
// ===========================================================================
38
// static member variables
39
// ===========================================================================
40
41
GeoConvHelper GeoConvHelper::myProcessing("!", Position(), Boundary(), Boundary());
42
GeoConvHelper GeoConvHelper::myLoaded("!", Position(), Boundary(), Boundary());
43
GeoConvHelper GeoConvHelper::myFinal("!", Position(), Boundary(), Boundary());
44
int GeoConvHelper::myNumLoaded = 0;
45
std::map<std::string, std::pair<std::string, Position> > GeoConvHelper::myLoadedPlain;
46
47
// ===========================================================================
48
// method definitions
49
// ===========================================================================
50
51
GeoConvHelper::GeoConvHelper(const std::string& proj, const Position& offset,
52
const Boundary& orig, const Boundary& conv, double scale, double rot, bool inverse, bool flatten):
53
myProjString(proj),
54
#ifdef PROJ_API_FILE
55
myProjection(nullptr),
56
myInverseProjection(nullptr),
57
myGeoProjection(nullptr),
58
#endif
59
myOffset(offset),
60
myGeoScale(scale),
61
mySin(sin(DEG2RAD(-rot))), // rotate clockwise
62
myCos(cos(DEG2RAD(-rot))),
63
myProjectionMethod(NONE),
64
myUseInverseProjection(inverse),
65
myFlatten(flatten),
66
myOrigBoundary(orig),
67
myConvBoundary(conv) {
68
if (proj == "!") {
69
myProjectionMethod = NONE;
70
} else if (proj == "-") {
71
myProjectionMethod = SIMPLE;
72
} else if (proj == "UTM") {
73
myProjectionMethod = UTM;
74
} else if (proj == "DHDN") {
75
myProjectionMethod = DHDN;
76
} else if (proj == "DHDN_UTM") {
77
myProjectionMethod = DHDN_UTM;
78
#ifdef PROJ_API_FILE
79
} else {
80
myProjectionMethod = PROJ;
81
initProj(proj);
82
if (myProjection == nullptr) {
83
// avoid error about missing datum shift file
84
myProjString = std::regex_replace(proj, std::regex("\\+geoidgrids[^ ]*"), std::string(""));
85
myProjString = std::regex_replace(myProjString, std::regex("\\+step \\+proj=vgridshift \\+grids[^ ]*"), std::string(""));
86
if (myProjString != proj) {
87
WRITE_WARNING(TL("Ignoring geoidgrids and vgridshift in projection"));
88
initProj(myProjString);
89
}
90
}
91
if (myProjection == nullptr) {
92
throw ProcessError(TL("Could not build projection!"));
93
}
94
#endif
95
}
96
}
97
98
99
#ifdef PROJ_API_FILE
100
void
101
GeoConvHelper::initProj(const std::string& proj) {
102
#ifdef PROJ_VERSION_MAJOR
103
myProjection = proj_create(PJ_DEFAULT_CTX, proj.c_str());
104
checkError(myProjection);
105
#else
106
myProjection = pj_init_plus(proj.c_str());
107
#endif
108
}
109
#endif
110
111
112
GeoConvHelper::~GeoConvHelper() {
113
#ifdef PROJ_API_FILE
114
if (myProjection != nullptr) {
115
#ifdef PROJ_VERSION_MAJOR
116
proj_destroy(myProjection);
117
#else
118
pj_free(myProjection);
119
#endif
120
}
121
if (myInverseProjection != nullptr) {
122
#ifdef PROJ_VERSION_MAJOR
123
proj_destroy(myInverseProjection);
124
#else
125
pj_free(myInverseProjection);
126
#endif
127
}
128
if (myGeoProjection != nullptr) {
129
#ifdef PROJ_VERSION_MAJOR
130
proj_destroy(myGeoProjection);
131
#else
132
pj_free(myGeoProjection);
133
#endif
134
}
135
#endif
136
}
137
138
bool
139
GeoConvHelper::operator==(const GeoConvHelper& o) const {
140
return (
141
myProjString == o.myProjString &&
142
myOffset == o.myOffset &&
143
myProjectionMethod == o.myProjectionMethod &&
144
myOrigBoundary == o.myOrigBoundary &&
145
myConvBoundary == o.myConvBoundary &&
146
myGeoScale == o.myGeoScale &&
147
myCos == o.myCos &&
148
mySin == o.mySin &&
149
myUseInverseProjection == o.myUseInverseProjection &&
150
myFlatten == o.myFlatten
151
);
152
}
153
154
GeoConvHelper&
155
GeoConvHelper::operator=(const GeoConvHelper& orig) {
156
myProjString = orig.myProjString;
157
myOffset = orig.myOffset;
158
myProjectionMethod = orig.myProjectionMethod;
159
myOrigBoundary = orig.myOrigBoundary;
160
myConvBoundary = orig.myConvBoundary;
161
myGeoScale = orig.myGeoScale;
162
myCos = orig.myCos;
163
mySin = orig.mySin;
164
myUseInverseProjection = orig.myUseInverseProjection;
165
myFlatten = orig.myFlatten;
166
#ifdef PROJ_API_FILE
167
if (myProjection != nullptr) {
168
#ifdef PROJ_VERSION_MAJOR
169
proj_destroy(myProjection);
170
#else
171
pj_free(myProjection);
172
#endif
173
myProjection = nullptr;
174
}
175
if (myInverseProjection != nullptr) {
176
#ifdef PROJ_VERSION_MAJOR
177
proj_destroy(myInverseProjection);
178
#else
179
pj_free(myInverseProjection);
180
#endif
181
myInverseProjection = nullptr;
182
}
183
if (myGeoProjection != nullptr) {
184
#ifdef PROJ_VERSION_MAJOR
185
proj_destroy(myGeoProjection);
186
#else
187
pj_free(myGeoProjection);
188
#endif
189
myGeoProjection = nullptr;
190
}
191
if (orig.myProjection != nullptr) {
192
#ifdef PROJ_VERSION_MAJOR
193
myProjection = proj_create(PJ_DEFAULT_CTX, orig.myProjString.c_str());
194
#else
195
myProjection = pj_init_plus(orig.myProjString.c_str());
196
#endif
197
}
198
if (orig.myInverseProjection != nullptr) {
199
#ifdef PROJ_VERSION_MAJOR
200
myInverseProjection = orig.myInverseProjection;
201
#else
202
myInverseProjection = pj_init_plus(pj_get_def(orig.myInverseProjection, 0));
203
#endif
204
}
205
if (orig.myGeoProjection != nullptr) {
206
#ifdef PROJ_VERSION_MAJOR
207
myGeoProjection = orig.myGeoProjection;
208
#else
209
myGeoProjection = pj_init_plus(pj_get_def(orig.myGeoProjection, 0));
210
#endif
211
}
212
#endif
213
return *this;
214
}
215
216
217
bool
218
GeoConvHelper::init(OptionsCont& oc) {
219
std::string proj = "!"; // the default
220
double scale = oc.getFloat("proj.scale");
221
double rot = oc.getFloat("proj.rotate");
222
Position offset = Position(oc.getFloat("offset.x"), oc.getFloat("offset.y"), oc.getFloat("offset.z"));
223
bool inverse = oc.exists("proj.inverse") && oc.getBool("proj.inverse");
224
bool flatten = oc.exists("flatten") && oc.getBool("flatten");
225
226
if (oc.getBool("simple-projection")) {
227
proj = "-";
228
}
229
230
#ifdef PROJ_API_FILE
231
if (oc.getBool("proj.inverse") && oc.getString("proj") == "!") {
232
WRITE_ERROR(TL("Inverse projection works only with explicit proj parameters."));
233
return false;
234
}
235
unsigned numProjections = oc.getBool("simple-projection") + oc.getBool("proj.utm") + oc.getBool("proj.dhdn") + oc.getBool("proj.dhdnutm") + (oc.getString("proj").length() > 1);
236
if (numProjections > 1) {
237
WRITE_ERROR(TL("The projection method needs to be uniquely defined."));
238
return false;
239
}
240
241
if (oc.getBool("proj.utm")) {
242
proj = "UTM";
243
} else if (oc.getBool("proj.dhdn")) {
244
proj = "DHDN";
245
} else if (oc.getBool("proj.dhdnutm")) {
246
proj = "DHDN_UTM";
247
} else if (!oc.isDefault("proj")) {
248
proj = oc.getString("proj");
249
}
250
#endif
251
myProcessing = GeoConvHelper(proj, offset, Boundary(), Boundary(), scale, rot, inverse, flatten);
252
myFinal = myProcessing;
253
return true;
254
}
255
256
257
void
258
GeoConvHelper::init(const std::string& proj, const Position& offset, const Boundary& orig,
259
const Boundary& conv, double scale) {
260
myProcessing = GeoConvHelper(proj, offset, orig, conv, scale);
261
myProcessing.resolveAbstractProjection();
262
myFinal = myProcessing;
263
}
264
265
void
266
GeoConvHelper::resolveAbstractProjection() {
267
#ifdef PROJ_API_FILE
268
if (myProjection == nullptr &&
269
myProjectionMethod != NONE && myProjectionMethod != SIMPLE) {
270
const std::string origProj = myProjString;
271
// try to initialized projection based on origBoundary
272
Position tmp = myOrigBoundary.getCenter();
273
x2cartesian(tmp, false);
274
if (myProjection == nullptr) {
275
WRITE_WARNING("Failed to intialized projection '" + origProj + "' based on origBoundary centered on '" + toString(myOrigBoundary.getCenter()) + "'");
276
myProjectionMethod = NONE;
277
}
278
}
279
#endif
280
}
281
282
void
283
GeoConvHelper::addProjectionOptions(OptionsCont& oc) {
284
oc.addOptionSubTopic("Projection");
285
286
oc.doRegister("simple-projection", new Option_Bool(false));
287
oc.addSynonyme("simple-projection", "proj.simple", true);
288
oc.addDescription("simple-projection", "Projection", TL("Uses a simple method for projection"));
289
290
oc.doRegister("proj.scale", new Option_Float(1.0));
291
oc.addDescription("proj.scale", "Projection", TL("Scaling factor for input coordinates"));
292
293
oc.doRegister("proj.rotate", new Option_Float(0.0));
294
oc.addDescription("proj.rotate", "Projection", TL("Rotation (clockwise degrees) for input coordinates"));
295
296
#ifdef PROJ_API_FILE
297
oc.doRegister("proj.utm", new Option_Bool(false));
298
oc.addDescription("proj.utm", "Projection", TL("Determine the UTM zone (for a universal transversal mercator projection based on the WGS84 ellipsoid)"));
299
300
oc.doRegister("proj.dhdn", new Option_Bool(false));
301
oc.addDescription("proj.dhdn", "Projection", "Determine the DHDN zone (for a transversal mercator projection based on the bessel ellipsoid, \"Gauss-Krueger\")");
302
303
oc.doRegister("proj", new Option_String("!"));
304
oc.addDescription("proj", "Projection", TL("Uses STR as proj.4 definition for projection"));
305
306
oc.doRegister("proj.inverse", new Option_Bool(false));
307
oc.addDescription("proj.inverse", "Projection", TL("Inverses projection"));
308
309
oc.doRegister("proj.dhdnutm", new Option_Bool(false));
310
oc.addDescription("proj.dhdnutm", "Projection", TL("Convert from Gauss-Krueger to UTM"));
311
#endif // PROJ_API_FILE
312
}
313
314
315
bool
316
GeoConvHelper::usingGeoProjection() const {
317
return myProjectionMethod != NONE;
318
}
319
320
321
bool
322
GeoConvHelper::usingInverseGeoProjection() const {
323
return myUseInverseProjection;
324
}
325
326
327
void
328
GeoConvHelper::cartesian2geo(Position& cartesian) const {
329
cartesian.sub(getOffsetBase());
330
if (myProjectionMethod == NONE) {
331
return;
332
}
333
if (myProjectionMethod == SIMPLE) {
334
const double y = cartesian.y() / 111136.;
335
const double x = cartesian.x() / 111320. / cos(DEG2RAD(y));
336
cartesian.set(x, y);
337
return;
338
}
339
#ifdef PROJ_API_FILE
340
#ifdef PROJ_VERSION_MAJOR
341
PJ_COORD c = proj_coord(cartesian.x(), cartesian.y(), cartesian.z(), 0);
342
c = proj_trans(myProjection, PJ_INV, c);
343
checkError(myProjection);
344
cartesian.set(proj_todeg(c.lp.lam), proj_todeg(c.lp.phi));
345
#else
346
projUV p;
347
p.u = cartesian.x();
348
p.v = cartesian.y();
349
p = pj_inv(p, myProjection);
350
//!!! check pj_errno
351
p.u *= RAD_TO_DEG;
352
p.v *= RAD_TO_DEG;
353
cartesian.set((double) p.u, (double) p.v);
354
#endif
355
#endif
356
}
357
358
359
#ifdef PROJ_API_FILE
360
#ifdef PROJ_VERSION_MAJOR
361
bool
362
GeoConvHelper::checkError(projPJ projection) const {
363
const int err_no = proj_context_errno(PJ_DEFAULT_CTX);
364
const char* err_string = "";
365
if (err_no != 0) {
366
#if PROJ_VERSION_MAJOR > 7
367
err_string = proj_context_errno_string(PJ_DEFAULT_CTX, err_no);
368
#else
369
err_string = proj_errno_string(err_no);
370
#endif
371
}
372
if (projection == nullptr) {
373
if (err_no == 0) {
374
WRITE_WARNING(TL("Failed to create transformation, reason unknown."));
375
} else {
376
WRITE_WARNINGF(TL("Failed to create transformation, %."), err_string);
377
}
378
return false;
379
}
380
if (err_no != 0) {
381
WRITE_WARNINGF(TL("Failed to transform, %."), err_string);
382
return false;
383
}
384
return true;
385
}
386
#endif
387
#endif
388
389
390
bool
391
GeoConvHelper::x2cartesian(Position& from, bool includeInBoundary) {
392
if (includeInBoundary) {
393
myOrigBoundary.add(from);
394
}
395
// init projection parameter on first use
396
#ifdef PROJ_API_FILE
397
if (myProjection == nullptr) {
398
double x = from.x() * myGeoScale;
399
switch (myProjectionMethod) {
400
case DHDN_UTM: {
401
int zone = (int)((x - 500000.) / 1000000.);
402
if (zone < 1 || zone > 5) {
403
WRITE_WARNING("Attempt to initialize DHDN_UTM-projection on invalid longitude " + toString(x));
404
return false;
405
}
406
myProjString = "+proj=tmerc +lat_0=0 +lon_0=" + toString(3 * zone) +
407
" +k=1 +x_0=" + toString(zone * 1000000 + 500000) +
408
" +y_0=0 +ellps=bessel +datum=potsdam +units=m +no_defs";
409
#ifdef PROJ_VERSION_MAJOR
410
myInverseProjection = proj_create(PJ_DEFAULT_CTX, myProjString.c_str());
411
checkError(myInverseProjection);
412
myGeoProjection = proj_create(PJ_DEFAULT_CTX, "+proj=latlong +datum=WGS84");
413
checkError(myGeoProjection);
414
#else
415
myInverseProjection = pj_init_plus(myProjString.c_str());
416
myGeoProjection = pj_init_plus("+proj=latlong +datum=WGS84");
417
#endif
418
x = ((x - 500000.) / 1000000.) * 3; // continues with UTM
419
}
420
FALLTHROUGH;
421
case UTM: {
422
int zone = (int)(x + 180) / 6 + 1;
423
myProjString = "+proj=utm +zone=" + toString(zone) +
424
" +ellps=WGS84 +datum=WGS84 +units=m +no_defs";
425
#ifdef PROJ_VERSION_MAJOR
426
myProjection = proj_create(PJ_DEFAULT_CTX, myProjString.c_str());
427
checkError(myProjection);
428
#else
429
myProjection = pj_init_plus(myProjString.c_str());
430
#endif
431
}
432
break;
433
case DHDN: {
434
int zone = (int)(x / 3);
435
if (zone < 1 || zone > 5) {
436
WRITE_WARNING("Attempt to initialize DHDN-projection on invalid longitude " + toString(x));
437
return false;
438
}
439
myProjString = "+proj=tmerc +lat_0=0 +lon_0=" + toString(3 * zone) +
440
" +k=1 +x_0=" + toString(zone * 1000000 + 500000) +
441
" +y_0=0 +ellps=bessel +datum=potsdam +units=m +no_defs";
442
#ifdef PROJ_VERSION_MAJOR
443
myProjection = proj_create(PJ_DEFAULT_CTX, myProjString.c_str());
444
checkError(myProjection);
445
#else
446
myProjection = pj_init_plus(myProjString.c_str());
447
#endif
448
}
449
break;
450
default:
451
break;
452
}
453
}
454
if (myInverseProjection != nullptr) {
455
#ifdef PROJ_VERSION_MAJOR
456
PJ_COORD c = proj_coord(from.x(), from.y(), from.z(), 0);
457
c = proj_trans(myInverseProjection, PJ_INV, c);
458
checkError(myInverseProjection);
459
from.set(proj_todeg(c.lp.lam), proj_todeg(c.lp.phi));
460
#else
461
double x = from.x();
462
double y = from.y();
463
if (pj_transform(myInverseProjection, myGeoProjection, 1, 1, &x, &y, nullptr)) {
464
WRITE_WARNINGF(TL("Could not transform (%,%)"), toString(x), toString(y));
465
}
466
from.set(double(x * RAD_TO_DEG), double(y * RAD_TO_DEG));
467
#endif
468
}
469
#endif
470
// perform conversion
471
bool ok = x2cartesian_const(from);
472
if (ok) {
473
if (includeInBoundary) {
474
myConvBoundary.add(from);
475
}
476
}
477
return ok;
478
}
479
480
481
bool
482
GeoConvHelper::x2cartesian_const(Position& from) const {
483
const double x2 = from.x() * myGeoScale;
484
const double y2 = from.y() * myGeoScale;
485
double x = x2 * myCos - y2 * mySin;
486
double y = x2 * mySin + y2 * myCos;
487
if (myProjectionMethod == NONE) {
488
// do nothing
489
} else if (myUseInverseProjection) {
490
cartesian2geo(from);
491
} else {
492
if (x > 180.1 || x < -180.1) {
493
WRITE_WARNING("Invalid longitude " + toString(x));
494
return false;
495
}
496
if (y > 90.1 || y < -90.1) {
497
WRITE_WARNING("Invalid latitude " + toString(y));
498
return false;
499
}
500
#ifdef PROJ_API_FILE
501
if (myProjection != nullptr) {
502
#ifdef PROJ_VERSION_MAJOR
503
PJ_COORD c = proj_coord(proj_torad(x), proj_torad(y), from.z(), 0);
504
c = proj_trans(myProjection, PJ_FWD, c);
505
checkError(myProjection);
506
x = c.xy.x;
507
y = c.xy.y;
508
#else
509
projUV p;
510
p.u = x * DEG_TO_RAD;
511
p.v = y * DEG_TO_RAD;
512
p = pj_fwd(p, myProjection);
513
//!!! check pj_errno
514
x = p.u;
515
y = p.v;
516
#endif
517
}
518
#endif
519
if (myProjectionMethod == SIMPLE) {
520
// Sinusoidal projection (https://en.wikipedia.org/wiki/Sinusoidal_projection)
521
x *= 111320. * cos(DEG2RAD(y));
522
y *= 111136.;
523
//!!! recheck whether the axes are mirrored
524
}
525
}
526
if (x > std::numeric_limits<double>::max() ||
527
y > std::numeric_limits<double>::max()) {
528
return false;
529
}
530
from.set(x, y);
531
from.add(myOffset);
532
if (myFlatten) {
533
from.setz(0);
534
}
535
return true;
536
}
537
538
539
void
540
GeoConvHelper::moveConvertedBy(double x, double y) {
541
myOffset.add(x, y);
542
myConvBoundary.moveby(x, y);
543
}
544
545
546
const Boundary&
547
GeoConvHelper::getOrigBoundary() const {
548
return myOrigBoundary;
549
}
550
551
552
const Boundary&
553
GeoConvHelper::getConvBoundary() const {
554
return myConvBoundary;
555
}
556
557
558
const Position
559
GeoConvHelper::getOffset() const {
560
return myOffset;
561
}
562
563
564
const Position
565
GeoConvHelper::getOffsetBase() const {
566
return myOffset;
567
}
568
569
570
const std::string&
571
GeoConvHelper::getProjString() const {
572
return myProjString;
573
}
574
575
576
void
577
GeoConvHelper::computeFinal(bool lefthand) {
578
if (myNumLoaded == 0) {
579
myFinal = myProcessing;
580
if (lefthand) {
581
myFinal.myOffset.mul(1, -1);
582
}
583
} else {
584
if (lefthand) {
585
myProcessing.myOffset.mul(1, -1);
586
}
587
myFinal = GeoConvHelper(
588
// prefer options over loaded location
589
myProcessing.usingGeoProjection() ? myProcessing.getProjString() : myLoaded.getProjString(),
590
// let offset and boundary lead back to the original coords of the loaded data
591
myProcessing.getOffset() + myLoaded.getOffset(),
592
myLoaded.getOrigBoundary(),
593
// the new boundary (updated during loading)
594
myProcessing.getConvBoundary());
595
}
596
if (lefthand) {
597
myFinal.myConvBoundary.flipY();
598
}
599
}
600
601
602
void
603
GeoConvHelper::setLoaded(const GeoConvHelper& loaded) {
604
myNumLoaded++;
605
if (myNumLoaded > 1) {
606
WRITE_WARNINGF(TL("Ignoring loaded location attribute nr. % for tracking of original location"), toString(myNumLoaded));
607
} else {
608
myLoaded = loaded;
609
}
610
}
611
612
613
void
614
GeoConvHelper::setLoadedPlain(const std::string& nodFile, const GeoConvHelper& loaded) {
615
myLoadedPlain[nodFile] = {loaded.getProjString(), loaded.getOffset()};
616
}
617
618
619
GeoConvHelper*
620
GeoConvHelper::getLoadedPlain(const std::string& plainFile, const std::string& suffix) {
621
std::string nodFile = StringUtils::replace(plainFile, suffix, ".nod.xml");
622
auto it = myLoadedPlain.find(nodFile);
623
if (it != myLoadedPlain.end()) {
624
return new GeoConvHelper(it->second.first, it->second.second, Boundary(), Boundary());
625
} else {
626
return nullptr;
627
}
628
}
629
630
631
void
632
GeoConvHelper::resetLoaded() {
633
myNumLoaded = 0;
634
myLoadedPlain.clear();
635
}
636
637
638
void
639
GeoConvHelper::writeLocation(OutputDevice& into) {
640
into.openTag(SUMO_TAG_LOCATION);
641
into.writeAttr(SUMO_ATTR_NET_OFFSET, myFinal.getOffsetBase());
642
into.writeAttr(SUMO_ATTR_CONV_BOUNDARY, myFinal.getConvBoundary());
643
if (myFinal.usingGeoProjection()) {
644
into.setPrecision(gPrecisionGeo);
645
}
646
into.writeAttr(SUMO_ATTR_ORIG_BOUNDARY, myFinal.getOrigBoundary());
647
if (myFinal.usingGeoProjection()) {
648
into.setPrecision();
649
}
650
into.writeAttr(SUMO_ATTR_ORIG_PROJ, myFinal.getProjString());
651
into.closeTag();
652
into.lf();
653
}
654
655
656
/****************************************************************************/
657
658