• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geographika / mapserver / 17823520225

18 Sep 2025 08:57AM UTC coverage: 41.442% (-0.02%) from 41.466%
17823520225

push

github

geographika
Use full path for CONFIG test

62112 of 149877 relevant lines covered (41.44%)

25354.57 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.85
/src/mapproject.c
1
/******************************************************************************
2
 * $Id$
3
 *
4
 * Project:  MapServer
5
 * Purpose:  projectionObj / PROJ.4 interface.
6
 * Author:   Steve Lime and the MapServer team.
7
 *
8
 ******************************************************************************
9
 * Copyright (c) 1996-2005 Regents of the University of Minnesota.
10
 *
11
 * Permission is hereby granted, free of charge, to any person obtaining a
12
 * copy of this software and associated documentation files (the "Software"),
13
 * to deal in the Software without restriction, including without limitation
14
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15
 * and/or sell copies of the Software, and to permit persons to whom the
16
 * Software is furnished to do so, subject to the following conditions:
17
 *
18
 * The above copyright notice and this permission notice shall be included in
19
 * all copies of this Software or works derived from this Software.
20
 *
21
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27
 * DEALINGS IN THE SOFTWARE.
28
 ****************************************************************************/
29

30
#include "mapserver.h"
31
#include "mapproject.h"
32
#include "mapthread.h"
33
#include <assert.h>
34
#include <float.h>
35
#include <sys/types.h>
36
#include <sys/stat.h>
37
#include "mapaxisorder.h"
38

39
#include "cpl_conv.h"
40
#include "cpl_string.h"
41
#include "ogr_srs_api.h"
42
#include "proj_experimental.h"
43

44
static char *ms_proj_data = NULL;
45
static unsigned ms_proj_data_change_counter = 0;
46

47
typedef struct LinkedListOfProjContext LinkedListOfProjContext;
48
struct LinkedListOfProjContext {
49
  LinkedListOfProjContext *next;
50
  projectionContext *context;
51
};
52

53
static LinkedListOfProjContext *headOfLinkedListOfProjContext = NULL;
54

55
static int msTestNeedWrap(pointObj pt1, pointObj pt2, pointObj pt2_geo,
56
                          reprojectionObj *reprojector);
57

58
static projectionContext *msProjectionContextCreate(void);
59
static void msProjectionContextUnref(projectionContext *ctx);
60

61
/* Helps considerably for use cases like msautotest/wxs/wms_inspire.map */
62
/* which involve a number of layers with same SRS, and a number of exposed */
63
/* output SRS */
64
#define PJ_CACHE_ENTRY_SIZE 32
65

66
typedef struct {
67
  char *inStr;
68
  char *outStr;
69
  PJ *pj;
70
} pjCacheEntry;
71

72
struct projectionContext {
73
  void *thread_id;
74
  PJ_CONTEXT *proj_ctx;
75
  unsigned ms_proj_data_change_counter;
76
  int refcount;
77
  pjCacheEntry pj_cache[PJ_CACHE_ENTRY_SIZE];
78
  int pj_cache_size;
79
  int cannotFindProjDb;
80
};
81

82
/************************************************************************/
83
/*                  msProjectHasLonWrapOrOver()                         */
84
/************************************************************************/
85

86
static int msProjectHasLonWrapOrOver(projectionObj *in) {
14,578✔
87
  int i;
88
  for (i = 0; i < in->numargs; i++) {
37,633✔
89
    if (strncmp(in->args[i], "lon_wrap=", strlen("lon_wrap=")) == 0 ||
32,537✔
90
        strcmp(in->args[i], "+over") == 0 || strcmp(in->args[i], "over") == 0) {
32,503✔
91
      return MS_TRUE;
92
    }
93
  }
94
  return MS_FALSE;
95
}
96

97
static char *getStringFromArgv(int argc, char **args) {
14,578✔
98
  int i;
99
  int len = 0;
100
  for (i = 0; i < argc; i++) {
47,141✔
101
    len += strlen(args[i]) + 1;
32,563✔
102
  }
103
  char *str = msSmallMalloc(len + 1);
14,578✔
104
  len = 0;
105
  for (i = 0; i < argc; i++) {
47,141✔
106
    size_t arglen = strlen(args[i]);
32,563✔
107
    memcpy(str + len, args[i], arglen);
32,563✔
108
    len += arglen;
32,563✔
109
    str[len] = ' ';
32,563✔
110
    len++;
32,563✔
111
  }
112
  str[len] = 0;
14,578✔
113
  return str;
14,578✔
114
}
115

116
/************************************************************************/
117
/*                         createNormalizedPJ()                         */
118
/************************************************************************/
119

120
/* Return to be freed with proj_destroy() if *pbFreePJ = TRUE */
121
static PJ *createNormalizedPJ(projectionObj *in, projectionObj *out,
7,297✔
122
                              int *pbFreePJ) {
123
  if (in->proj == out->proj) {
7,297✔
124
    /* Special case to avoid out_str below to cause in_str to become invalid */
125
    *pbFreePJ = TRUE;
8✔
126
    return proj_create(in->proj_ctx->proj_ctx, "+proj=noop");
8✔
127
  }
128

129
  const char *const wkt_options[] = {"MULTILINE=NO", NULL};
7,289✔
130
  const char *in_str = msProjectHasLonWrapOrOver(in)
7,289✔
131
                           ? proj_as_proj_string(in->proj_ctx->proj_ctx,
4,772✔
132
                                                 in->proj, PJ_PROJ_4, NULL)
133
                           : proj_as_wkt(in->proj_ctx->proj_ctx, in->proj,
7,289✔
134
                                         PJ_WKT2_2018, wkt_options);
135
  const char *out_str = msProjectHasLonWrapOrOver(out)
7,289✔
136
                            ? proj_as_proj_string(out->proj_ctx->proj_ctx,
4,710✔
137
                                                  out->proj, PJ_PROJ_4, NULL)
4,710✔
138
                            : proj_as_wkt(out->proj_ctx->proj_ctx, out->proj,
7,289✔
139
                                          PJ_WKT2_2018, wkt_options);
140
  PJ *pj_raw;
141
  PJ *pj_normalized;
142
  if (!in_str || !out_str)
7,289✔
143
    return NULL;
144
  char *in_str_for_cache = getStringFromArgv(in->numargs, in->args);
7,289✔
145
  char *out_str_for_cache = getStringFromArgv(out->numargs, out->args);
7,289✔
146

147
  if (in->proj_ctx->proj_ctx == out->proj_ctx->proj_ctx) {
7,289✔
148
    int i;
149
    pjCacheEntry *pj_cache = in->proj_ctx->pj_cache;
7,254✔
150
    for (i = 0; i < in->proj_ctx->pj_cache_size; i++) {
38,801✔
151
      if (strcmp(pj_cache[i].inStr, in_str_for_cache) == 0 &&
35,163✔
152
          strcmp(pj_cache[i].outStr, out_str_for_cache) == 0) {
20,863✔
153
        PJ *ret = pj_cache[i].pj;
3,616✔
154
        if (i != 0) {
3,616✔
155
          /* Move entry to top */
156
          pjCacheEntry tmp;
157
          memcpy(&tmp, &pj_cache[i], sizeof(pjCacheEntry));
158
          memmove(&pj_cache[1], &pj_cache[0], i * sizeof(pjCacheEntry));
2,443✔
159
          memcpy(&pj_cache[0], &tmp, sizeof(pjCacheEntry));
160
        }
161
#ifdef notdef
162
        fprintf(stderr, "cache hit!\n");
163
#endif
164
        *pbFreePJ = FALSE;
3,616✔
165
        msFree(in_str_for_cache);
3,616✔
166
        msFree(out_str_for_cache);
3,616✔
167
        return ret;
3,616✔
168
      }
169
    }
170
  }
171

172
#ifdef notdef
173
  fprintf(stderr, "%s -> %s\n", in_str, out_str);
174
  fprintf(stderr, "%p -> %p\n", in->proj_ctx->proj_ctx,
175
          out->proj_ctx->proj_ctx);
176
  fprintf(stderr, "cache miss!\n");
177
#endif
178

179
#if PROJ_VERSION_MAJOR == 6 && PROJ_VERSION_MINOR < 2
180
  if (strstr(in_str, "+proj=") && strstr(in_str, "+over") &&
181
      strstr(out_str, "+proj=") && strstr(out_str, "+over") &&
182
      strlen(in_str) < 400 && strlen(out_str) < 400) {
183
    // Fixed per PROJ commit
184
    // https://github.com/OSGeo/PROJ/commit/78302efb70eb4b49610cda6a60bf9ce39b82264f
185
    // and
186
    // https://github.com/OSGeo/PROJ/commit/ae70b26b9cbae85a38d5b26533ba06da0ea13940
187
    // Fix for wcs_get_capabilities_tileindexmixedsrs_26711.xml and
188
    // wcs_20_getcov_bands_name_new_reproject.dat
189
    char szPipeline[1024];
190
    strcpy(szPipeline, "+proj=pipeline");
191
    if (msProjIsGeographicCRS(in)) {
192
      strcat(szPipeline, " +step +proj=unitconvert +xy_in=deg +xy_out=rad");
193
    }
194
    strcat(szPipeline, " +step +inv ");
195
    strcat(szPipeline, in_str);
196
    strcat(szPipeline, " +step ");
197
    strcat(szPipeline, out_str);
198
    if (msProjIsGeographicCRS(out)) {
199
      strcat(szPipeline, " +step +proj=unitconvert +xy_in=rad +xy_out=deg");
200
    }
201
    /* We do not want datum=NAD83 to imply a transformation with towgs84=0,0,0
202
     */
203
    {
204
      char *ptr = szPipeline;
205
      while (1) {
206
        ptr = strstr(ptr, " +datum=NAD83");
207
        if (!ptr)
208
          break;
209
        memcpy(ptr, " +ellps=GRS80", 13);
210
      }
211
    }
212

213
    /* Remove +nadgrids=@null as it doesn't work if going outside of [-180,180]
214
     */
215
    /* Fixed per
216
     * https://github.com/OSGeo/PROJ/commit/10a30bb539be1afb25952b19af8bbe72e1b13b56
217
     */
218
    {
219
      char *ptr = strstr(szPipeline, " +nadgrids=@null");
220
      if (ptr)
221
        memcpy(ptr, "                ", 16);
222
    }
223

224
    pj_normalized = proj_create(in->proj_ctx->proj_ctx, szPipeline);
225
  } else
226
#endif
227
  {
228
#if PROJ_VERSION_MAJOR > 6 ||                                                  \
229
    (PROJ_VERSION_MAJOR == 6 && PROJ_VERSION_MINOR >= 2)
230
    pj_raw = proj_create_crs_to_crs_from_pj(in->proj_ctx->proj_ctx, in->proj,
3,673✔
231
                                            out->proj, NULL, NULL);
3,673✔
232
#else
233
    pj_raw =
234
        proj_create_crs_to_crs(in->proj_ctx->proj_ctx, in_str, out_str, NULL);
235
#endif
236
    if (!pj_raw) {
3,673✔
237
      msFree(in_str_for_cache);
×
238
      msFree(out_str_for_cache);
×
239
      return NULL;
×
240
    }
241
    pj_normalized =
242
        proj_normalize_for_visualization(in->proj_ctx->proj_ctx, pj_raw);
3,673✔
243
    proj_destroy(pj_raw);
3,673✔
244
  }
245
  if (!pj_normalized) {
3,673✔
246
    msFree(in_str_for_cache);
×
247
    msFree(out_str_for_cache);
×
248
    return NULL;
×
249
  }
250

251
  if (in->proj_ctx->proj_ctx == out->proj_ctx->proj_ctx) {
3,673✔
252
    /* Insert entry into cache */
253
    int i;
254
    pjCacheEntry *pj_cache = in->proj_ctx->pj_cache;
3,638✔
255
    if (in->proj_ctx->pj_cache_size < PJ_CACHE_ENTRY_SIZE) {
3,638✔
256
      i = in->proj_ctx->pj_cache_size;
257
      assert(pj_cache[i].inStr == NULL);
258
      in->proj_ctx->pj_cache_size++;
3,638✔
259
    } else {
260
      i = 0;
261
      /* Evict oldest entry */
262
      msFree(pj_cache[PJ_CACHE_ENTRY_SIZE - 1].inStr);
×
263
      msFree(pj_cache[PJ_CACHE_ENTRY_SIZE - 1].outStr);
×
264
      proj_destroy(pj_cache[PJ_CACHE_ENTRY_SIZE - 1].pj);
×
265
      memmove(&pj_cache[1], &pj_cache[0],
×
266
              (PJ_CACHE_ENTRY_SIZE - 1) * sizeof(pjCacheEntry));
267
    }
268
    pj_cache[i].inStr = msStrdup(in_str_for_cache);
3,638✔
269
    pj_cache[i].outStr = msStrdup(out_str_for_cache);
3,638✔
270
    pj_cache[i].pj = pj_normalized;
3,638✔
271
    *pbFreePJ = FALSE;
3,638✔
272
  } else {
273
    *pbFreePJ = TRUE;
35✔
274
  }
275

276
  msFree(in_str_for_cache);
3,673✔
277
  msFree(out_str_for_cache);
3,673✔
278

279
  return pj_normalized;
3,673✔
280
}
281

282
/************************************************************************/
283
/*                        getBaseGeographicCRS()                        */
284
/************************************************************************/
285

286
/* Return to be freed with proj_destroy() */
287
static PJ *getBaseGeographicCRS(projectionObj *in) {
256✔
288
  PJ_CONTEXT *ctxt;
289
  PJ *geod_crs;
290
  PJ *datum;
291
  PJ *cs;
292
  PJ *geog_crs;
293
  assert(in && in->proj);
294
  ctxt = in->proj_ctx->proj_ctx;
256✔
295
  geod_crs = proj_crs_get_geodetic_crs(ctxt, in->proj);
256✔
296
  if (geod_crs == NULL)
256✔
297
    return NULL;
298
#if PROJ_VERSION_MAJOR >= 8 ||                                                 \
299
    (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
300
  datum = proj_crs_get_datum_forced(ctxt, geod_crs);
256✔
301
#else
302
  datum = proj_crs_get_datum(ctxt, geod_crs);
303
#endif
304
  cs = proj_create_ellipsoidal_2D_cs(ctxt, PJ_ELLPS2D_LONGITUDE_LATITUDE, NULL,
256✔
305
                                     0);
306
  geog_crs = proj_create_geographic_crs_from_datum(ctxt, NULL, datum, cs);
256✔
307
  proj_destroy(geod_crs);
256✔
308
  proj_destroy(datum);
256✔
309
  proj_destroy(cs);
256✔
310
  if (proj_get_type(in->proj) == PJ_TYPE_BOUND_CRS) {
256✔
311
    PJ *hub_crs;
312
    PJ *transf;
313
    PJ *ret;
314
    hub_crs = proj_get_target_crs(ctxt, in->proj);
×
315
    transf = proj_crs_get_coordoperation(ctxt, in->proj);
×
316
    ret = proj_crs_create_bound_crs(ctxt, geog_crs, hub_crs, transf);
×
317
    proj_destroy(geog_crs);
×
318
    geog_crs = ret;
319
    proj_destroy(hub_crs);
×
320
    proj_destroy(transf);
×
321
  }
322

323
  return geog_crs;
324
}
325

326
/************************************************************************/
327
/*                          msProjErrorLogger()                         */
328
/************************************************************************/
329

330
static void msProjErrorLogger(void *user_data, int level, const char *message) {
5,819✔
331
  projectionContext *ctx = (projectionContext *)user_data;
332
  if (strstr(message, "Cannot find proj.db"))
5,819✔
333
    ctx->cannotFindProjDb = MS_TRUE;
×
334
  if (level == PJ_LOG_ERROR && msGetGlobalDebugLevel() >= MS_DEBUGLEVEL_VV) {
5,819✔
335
    msDebug("PROJ: Error: %s\n", message);
×
336
  } else if (level == PJ_LOG_DEBUG) {
5,819✔
337
    msDebug("PROJ: Debug: %s\n", message);
×
338
  }
339
}
5,819✔
340

341
/************************************************************************/
342
/*                        msProjGetProjErrorString()                    */
343
/************************************************************************/
344

345
static const char *msProjGetProjErrorString(projectionObj *p) {
19✔
346
  const int l_pj_errno = proj_context_errno(p->proj_ctx->proj_ctx);
19✔
347
#if PROJ_VERSION_MAJOR >= 8
348
  const char *projErrorStr =
349
      proj_context_errno_string(p->proj_ctx->proj_ctx, l_pj_errno);
19✔
350
#else
351
  const char *projErrorStr = proj_errno_string(l_pj_errno);
352
#endif
353
  if (p->proj_ctx->cannotFindProjDb) {
19✔
354
    projErrorStr = "Cannot find proj.db";
355
    p->proj_ctx->cannotFindProjDb = MS_FALSE;
×
356
  }
357
  return projErrorStr;
19✔
358
}
359

360
/************************************************************************/
361
/*                        msProjectionContextCreate()                   */
362
/************************************************************************/
363

364
projectionContext *msProjectionContextCreate(void) {
2,684✔
365
  projectionContext *ctx =
366
      (projectionContext *)msSmallCalloc(1, sizeof(projectionContext));
2,684✔
367
  ctx->thread_id = msGetThreadId();
2,684✔
368
  ctx->proj_ctx = proj_context_create();
2,684✔
369
  if (ctx->proj_ctx == NULL) {
2,684✔
370
    msFree(ctx);
×
371
    return NULL;
×
372
  }
373
  MS_REFCNT_INIT(ctx);
2,684✔
374
  proj_context_use_proj4_init_rules(ctx->proj_ctx, TRUE);
2,684✔
375
  proj_log_func(ctx->proj_ctx, ctx, msProjErrorLogger);
2,684✔
376
  return ctx;
2,684✔
377
}
378

379
/************************************************************************/
380
/*                        msProjectionContextUnref()                    */
381
/************************************************************************/
382

383
void msProjectionContextUnref(projectionContext *ctx) {
43,618✔
384
  if (!ctx)
43,618✔
385
    return;
386
  if (MS_REFCNT_DECR_IS_ZERO(ctx)) {
36,853✔
387
    int i;
388
    for (i = 0; i < ctx->pj_cache_size; i++) {
6,304✔
389
      msFree(ctx->pj_cache[i].inStr);
3,635✔
390
      msFree(ctx->pj_cache[i].outStr);
3,635✔
391
      proj_destroy(ctx->pj_cache[i].pj);
3,635✔
392
    }
393
    proj_context_destroy(ctx->proj_ctx);
2,669✔
394
    msFree(ctx);
2,669✔
395
  }
396
}
397

398
/************************************************************************/
399
/*                        msProjectCreateReprojector()                  */
400
/************************************************************************/
401

402
/* The pointed objects need to remain valid during the lifetime of the
403
 * reprojector, as only their pointer is stored.
404
 *
405
 * If the *content* of in and/or out is changed, then the reprojector is
406
 * no longer valid. This can be detected with msProjectIsReprojectorStillValid()
407
 */
408
reprojectionObj *msProjectCreateReprojector(projectionObj *in,
7,670✔
409
                                            projectionObj *out) {
410
  reprojectionObj *obj =
411
      (reprojectionObj *)msSmallCalloc(1, sizeof(reprojectionObj));
7,670✔
412
  obj->in = in;
7,670✔
413
  obj->out = out;
7,670✔
414
  obj->generation_number_in = in ? in->generation_number : 0;
7,670✔
415
  obj->generation_number_out = out ? out->generation_number : 0;
7,670✔
416
  obj->lineCuttingCase = LINE_CUTTING_UNKNOWN;
7,670✔
417

418
  /* -------------------------------------------------------------------- */
419
  /*      If the source and destination are simple and equal, then do     */
420
  /*      nothing.                                                        */
421
  /* -------------------------------------------------------------------- */
422
  if (in && in->numargs == 1 && out && out->numargs == 1 &&
7,670✔
423
      strcmp(in->args[0], out->args[0]) == 0) {
1,143✔
424
    /* do nothing, no transformation required */
425
  }
426
  /* -------------------------------------------------------------------- */
427
  /*      If we have a fully defined input coordinate system and          */
428
  /*      output coordinate system, then we will use createNormalizedPJ   */
429
  /* -------------------------------------------------------------------- */
430
  else if (in && in->proj && out && out->proj) {
7,303✔
431
    PJ *pj = createNormalizedPJ(in, out, &(obj->bFreePJ));
7,041✔
432
    if (!pj) {
7,041✔
433
      msFree(obj);
×
434
      return NULL;
×
435
    }
436
    obj->pj = pj;
7,041✔
437
  }
438

439
  /* nothing to do if the other coordinate system is also lat/long */
440
  else if ((in == NULL || in->proj == NULL) &&
262✔
441
           (out == NULL || out->proj == NULL || msProjIsGeographicCRS(out))) {
5✔
442
    /* do nothing */
443
  } else if (out == NULL && in != NULL && msProjIsGeographicCRS(in)) {
257✔
444
    /* do nothing */
445
  }
446

447
  /* -------------------------------------------------------------------- */
448
  /*      Otherwise we assume that the NULL projectionObj is supposed to be */
449
  /*      lat/long in the same datum as the other projectionObj.  This    */
450
  /*      is essentially a backwards compatibility mode.                  */
451
  /* -------------------------------------------------------------------- */
452
  else {
453
    PJ *pj = NULL;
454

455
    if ((in == NULL || in->proj == NULL) && out &&
256✔
456
        out->proj) { /* input coordinates are lat/long */
10✔
457
      PJ *source_crs = getBaseGeographicCRS(out);
5✔
458
      projectionObj in_modified;
459
      memset(&in_modified, 0, sizeof(in_modified));
460

461
      in_modified.proj_ctx = out->proj_ctx;
5✔
462
      in_modified.proj = source_crs;
5✔
463
      pj = createNormalizedPJ(&in_modified, out, &(obj->bFreePJ));
5✔
464
      proj_destroy(source_crs);
5✔
465
    } else if (/* (out==NULL || out->proj==NULL) && */ in && in->proj) {
251✔
466
      PJ *target_crs = getBaseGeographicCRS(in);
251✔
467
      projectionObj out_modified;
468
      memset(&out_modified, 0, sizeof(out_modified));
469

470
      out_modified.proj_ctx = in->proj_ctx;
251✔
471
      out_modified.proj = target_crs;
251✔
472
      pj = createNormalizedPJ(in, &out_modified, &(obj->bFreePJ));
251✔
473
      proj_destroy(target_crs);
251✔
474
    }
475
    if (!pj) {
256✔
476
      msFree(obj);
×
477
      return NULL;
×
478
    }
479
    obj->pj = pj;
256✔
480
  }
481

482
  return obj;
483
}
484

485
/************************************************************************/
486
/*                      msProjectDestroyReprojector()                   */
487
/************************************************************************/
488

489
void msProjectDestroyReprojector(reprojectionObj *reprojector) {
35,471✔
490
  if (!reprojector)
35,471✔
491
    return;
492
  if (reprojector->bFreePJ)
7,670✔
493
    proj_destroy(reprojector->pj);
43✔
494
  msFreeShape(&(reprojector->splitShape));
7,670✔
495
  msFree(reprojector);
7,670✔
496
}
497

498
/************************************************************************/
499
/*                       msProjectTransformPoints()                     */
500
/************************************************************************/
501

502
int msProjectTransformPoints(reprojectionObj *reprojector, int npoints,
41,485✔
503
                             double *x, double *y) {
504
  proj_trans_generic(reprojector->pj, PJ_FWD, x, sizeof(double), npoints, y,
41,485✔
505
                     sizeof(double), npoints, NULL, 0, 0, NULL, 0, 0);
506
  return MS_SUCCESS;
41,485✔
507
}
508

509
/*
510
** Initialize, load and free a projectionObj structure
511
*/
512
int msInitProjection(projectionObj *p) {
34,289✔
513
  p->gt.need_geotransform = MS_FALSE;
34,289✔
514
  p->is_polar = -1;
34,289✔
515
  p->numargs = 0;
34,289✔
516
  p->args = NULL;
517
  p->wellknownprojection = wkp_none;
34,289✔
518
  p->proj = NULL;
34,289✔
519
  p->args = (char **)malloc(MS_MAXPROJARGS * sizeof(char *));
34,289✔
520
  MS_CHECK_ALLOC(p->args, MS_MAXPROJARGS * sizeof(char *), -1);
34,289✔
521
  p->proj_ctx = NULL;
34,289✔
522
  p->generation_number = 0;
34,289✔
523
  return (0);
34,289✔
524
}
525

526
void msFreeProjection(projectionObj *p) {
40,989✔
527
  proj_destroy(p->proj);
40,989✔
528
  p->proj = NULL;
40,989✔
529
  msProjectionContextUnref(p->proj_ctx);
40,989✔
530
  p->proj_ctx = NULL;
40,989✔
531

532
  p->gt.need_geotransform = MS_FALSE;
40,989✔
533
  p->is_polar = -1;
40,989✔
534
  p->wellknownprojection = wkp_none;
40,989✔
535
  msFreeCharArray(p->args, p->numargs);
40,989✔
536
  p->args = NULL;
40,989✔
537
  p->numargs = 0;
40,989✔
538
  p->generation_number++;
40,989✔
539
}
40,989✔
540

541
void msFreeProjectionExceptContext(projectionObj *p) {
6,742✔
542
  projectionContext *ctx = p->proj_ctx;
6,742✔
543
  p->proj_ctx = NULL;
6,742✔
544
  msFreeProjection(p);
6,742✔
545
  p->proj_ctx = ctx;
6,742✔
546
}
6,742✔
547

548
/************************************************************************/
549
/*                      msProjectionContextClone()                      */
550
/************************************************************************/
551

552
static projectionContext *
553
msProjectionContextClone(const projectionContext *ctxSrc) {
×
554
  projectionContext *ctx = msProjectionContextCreate();
×
555
  if (ctx) {
×
556
    ctx->pj_cache_size = ctxSrc->pj_cache_size;
×
557
    for (int i = 0; i < ctx->pj_cache_size; ++i) {
×
558
      pjCacheEntry *entryDst = &(ctx->pj_cache[i]);
559
      const pjCacheEntry *entrySrc = &(ctxSrc->pj_cache[i]);
560
      entryDst->inStr = msStrdup(entrySrc->inStr);
×
561
      entryDst->outStr = msStrdup(entrySrc->outStr);
×
562
      entryDst->pj = proj_clone(
×
563
          /* use target PROJ context for cloning */
564
          ctx->proj_ctx, entrySrc->pj);
×
565
    }
566
  }
567
  return ctx;
×
568
}
569

570
/************************************************************************/
571
/*                 msProjectionInheritContextFrom()                     */
572
/************************************************************************/
573

574
void msProjectionInheritContextFrom(projectionObj *pDst,
28,125✔
575
                                    const projectionObj *pSrc) {
576
  if (pDst->proj_ctx == NULL && pSrc->proj_ctx != NULL) {
28,125✔
577
    if (pSrc->proj_ctx->thread_id == msGetThreadId()) {
27,900✔
578
      pDst->proj_ctx = pSrc->proj_ctx;
27,900✔
579
      MS_REFCNT_INCR(pDst->proj_ctx);
27,900✔
580
    } else {
581
      pDst->proj_ctx = msProjectionContextClone(pSrc->proj_ctx);
×
582
    }
583
  }
584
}
28,125✔
585

586
/************************************************************************/
587
/*                      msProjectionSetContext()                        */
588
/************************************************************************/
589

590
void msProjectionSetContext(projectionObj *p, projectionContext *ctx) {
6,336✔
591
  if (p->proj_ctx == NULL && ctx != NULL) {
6,336✔
592
    p->proj_ctx = ctx;
6,336✔
593
    MS_REFCNT_INCR(p->proj_ctx);
6,336✔
594
  }
595
}
6,336✔
596

597
/*
598
** Handle OGC WMS/WFS AUTO projection in the format:
599
**    "AUTO:proj_id,units_id,lon0,lat0"
600
*/
601

602
static int _msProcessAutoProjection(projectionObj *p) {
×
603
  char **args;
604
  int numargs, nProjId, nUnitsId, nZone;
605
  double dLat0, dLon0;
606
  const char *pszUnits = "m";
607
  char szProjBuf[512] = "";
×
608

609
  /* WMS/WFS AUTO projection: "AUTO:proj_id,units_id,lon0,lat0" */
610
  args = msStringSplit(p->args[0], ',', &numargs);
×
611
  if (numargs != 4 || (strncasecmp(args[0], "AUTO:", 5) != 0 &&
×
612
                       strncasecmp(args[0], "AUTO2:", 6) != 0)) {
×
613
    msSetError(MS_PROJERR,
×
614
               "WMS/WFS AUTO/AUTO2 PROJECTION must be in the format "
615
               "'AUTO:proj_id,units_id,lon0,lat0' or "
616
               "'AUTO2:crs_id,factor,lon0,lat0'(got '%s').\n",
617
               "_msProcessAutoProjection()", p->args[0]);
×
618
    msFreeCharArray(args, numargs);
×
619
    return -1;
×
620
  }
621

622
  if (strncasecmp(args[0], "AUTO:", 5) == 0)
×
623
    nProjId = atoi(args[0] + 5);
×
624
  else
625
    nProjId = atoi(args[0] + 6);
×
626

627
  nUnitsId = atoi(args[1]);
×
628
  dLon0 = atof(args[2]);
×
629
  dLat0 = atof(args[3]);
×
630

631
  /*There is no unit parameter for AUTO2. The 2nd parameter is
632
   factor. Set the units to always be meter*/
633
  if (strncasecmp(args[0], "AUTO2:", 6) == 0)
×
634
    nUnitsId = 9001;
635

636
  msFreeCharArray(args, numargs);
×
637

638
  /* Handle EPSG Units.  Only meters for now. */
639
  switch (nUnitsId) {
×
640
  case 9001: /* Meters */
×
641
    pszUnits = "m";
642
    break;
643
  default:
×
644
    msSetError(MS_PROJERR,
×
645
               "WMS/WFS AUTO PROJECTION: EPSG Units %d not supported.\n",
646
               "_msProcessAutoProjection()", nUnitsId);
647
    return -1;
×
648
  }
649

650
  /* Build PROJ4 definition.
651
   * This is based on the definitions found in annex E of the WMS 1.1.1
652
   * spec and online at http://www.digitalearth.org/wmt/auto.html
653
   * The conversion from the original WKT definitions to PROJ4 format was
654
   * done using the MapScript setWKTProjection() function (based on OGR).
655
   */
656
  switch (nProjId) {
×
657
  case 42001: /** WGS 84 / Auto UTM **/
×
658
    nZone = (int)floor((dLon0 + 180.0) / 6.0) + 1;
×
659
    sprintf(szProjBuf,
×
660
            "+proj=tmerc+lat_0=0+lon_0=%.16g+k=0.999600+x_0=500000"
661
            "+y_0=%.16g+ellps=WGS84+datum=WGS84+units=%s+type=crs",
662
            -183.0 + nZone * 6.0, (dLat0 >= 0.0) ? 0.0 : 10000000.0, pszUnits);
×
663
    break;
664
  case 42002: /** WGS 84 / Auto Tr. Mercator **/
×
665
    sprintf(szProjBuf,
×
666
            "+proj=tmerc+lat_0=0+lon_0=%.16g+k=0.999600+x_0=500000"
667
            "+y_0=%.16g+ellps=WGS84+datum=WGS84+units=%s+type=crs",
668
            dLon0, (dLat0 >= 0.0) ? 0.0 : 10000000.0, pszUnits);
669
    break;
670
  case 42003: /** WGS 84 / Auto Orthographic **/
671
    sprintf(szProjBuf,
672
            "+proj=ortho+lon_0=%.16g+lat_0=%.16g+x_0=0+y_0=0"
673
            "+ellps=WGS84+datum=WGS84+units=%s+type=crs",
674
            dLon0, dLat0, pszUnits);
675
    break;
676
  case 42004: /** WGS 84 / Auto Equirectangular **/
677
    /* Note that we have to pass lon_0 as lon_ts for this one to */
678
    /* work.  Either a PROJ4 bug or a PROJ4 documentation issue. */
679
    sprintf(szProjBuf,
680
            "+proj=eqc+lon_ts=%.16g+lat_ts=%.16g+x_0=0+y_0=0"
681
            "+ellps=WGS84+datum=WGS84+units=%s+type=crs",
682
            dLon0, dLat0, pszUnits);
683
    break;
684
  case 42005: /** WGS 84 / Auto Mollweide **/
685
    sprintf(szProjBuf,
686
            "+proj=moll+lon_0=%.16g+x_0=0+y_0=0+ellps=WGS84"
687
            "+datum=WGS84+units=%s+type=crs",
688
            dLon0, pszUnits);
689
    break;
690
  default:
×
691
    msSetError(MS_PROJERR, "WMS/WFS AUTO PROJECTION %d not supported.\n",
×
692
               "_msProcessAutoProjection()", nProjId);
693
    return -1;
×
694
  }
695

696
  /* msDebug("%s = %s\n", p->args[0], szProjBuf); */
697

698
  /* OK, pass the definition to pj_init() */
699
  args = msStringSplit(szProjBuf, '+', &numargs);
×
700

701
  if (!(p->proj = proj_create_argv(p->proj_ctx->proj_ctx, numargs, args))) {
×
702
    msSetError(MS_PROJERR, "PROJ error \"%s\" when instantiating \"%s\"",
×
703
               "msProcessProjection()", msProjGetProjErrorString(p), szProjBuf);
704
    msFreeCharArray(args, numargs);
×
705
    return (-1);
×
706
  }
707

708
  msFreeCharArray(args, numargs);
×
709

710
  return (0);
×
711
}
712

713
int msProcessProjection(projectionObj *p) {
29,635✔
714
  assert(p->proj == NULL);
715

716
  p->generation_number++;
29,635✔
717

718
  if (strcasecmp(p->args[0], "GEOGRAPHIC") == 0) {
29,635✔
719
    msSetError(MS_PROJERR,
×
720
               "PROJECTION 'GEOGRAPHIC' no longer supported.\n"
721
               "Provide explicit definition.\n"
722
               "ie. proj=latlong\n"
723
               "    ellps=clrk66\n",
724
               "msProcessProjection()");
725
    return (-1);
×
726
  }
727

728
  if (strcasecmp(p->args[0], "AUTO") == 0) {
29,635✔
729
    p->proj = NULL;
17✔
730
    return 0;
17✔
731
  }
732

733
  if (p->proj_ctx == NULL) {
29,618✔
734
    p->proj_ctx = msProjectionContextCreate();
40✔
735
    if (p->proj_ctx == NULL) {
40✔
736
      return -1;
737
    }
738
  }
739
  if (p->proj_ctx->ms_proj_data_change_counter != ms_proj_data_change_counter) {
29,618✔
740
    msAcquireLock(TLOCK_PROJ);
×
741
    p->proj_ctx->ms_proj_data_change_counter = ms_proj_data_change_counter;
×
742
    {
743
      if (ms_proj_data) {
×
744
        int num_tokens = 0;
×
745
#ifdef _WIN32
746
        char sep = ';';
747
#else
748
        char sep = ':';
749
#endif
750
        char **paths = msStringSplit(ms_proj_data, sep, &num_tokens);
×
751
        proj_context_set_search_paths(p->proj_ctx->proj_ctx, num_tokens,
×
752
                                      (const char *const *)paths);
753
        msFreeCharArray(paths, num_tokens);
×
754
      } else {
755
        proj_context_set_search_paths(p->proj_ctx->proj_ctx, 0, NULL);
×
756
      }
757
    }
758
    msReleaseLock(TLOCK_PROJ);
×
759
  }
760

761
  if (strncasecmp(p->args[0], "AUTO:", 5) == 0 ||
29,618✔
762
      strncasecmp(p->args[0], "AUTO2:", 6) == 0) {
29,618✔
763
    /* WMS/WFS AUTO projection: "AUTO:proj_id,units_id,lon0,lat0" */
764
    /*WMS 1.3.0: AUTO2:auto_crs_id,factor,lon0,lat0*/
765
    return _msProcessAutoProjection(p);
×
766
  }
767

768
  if (p->numargs == 1 && strncmp(p->args[0], "init=", 5) != 0) {
29,618✔
769
    /* Deal e.g. with EPSG:XXXX or ESRI:XXXX */
770
    if (!(p->proj =
19✔
771
              proj_create_argv(p->proj_ctx->proj_ctx, p->numargs, p->args))) {
19✔
772
      msSetError(MS_PROJERR, "PROJ error \"%s\" when instantiating \"%s\"",
×
773
                 "msProcessProjection()", msProjGetProjErrorString(p),
774
                 p->args[0]);
×
775
      return (-1);
×
776
    }
777
  } else {
778
    // Reserve one extra slot for terminating "type=crs"
779
    char **args = (char **)msSmallMalloc(sizeof(char *) * (p->numargs + 1));
29,599✔
780
    int numargs = 0;
781
    for (int i = 0; i < p->numargs; ++i) {
82,671✔
782
      // PROJ doesn't like extraneous parameters that it doesn't recognize
783
      // when initializing a CRS from a +init=epsg:xxxx string
784
      // Cf https://github.com/OSGeo/PROJ/issues/4203
785
      if (strstr(p->args[i], "epsgaxis=") == NULL) {
53,072✔
786
        args[numargs] = p->args[i];
46,844✔
787
        ++numargs;
46,844✔
788
      }
789
    }
790

791
#if PROJ_VERSION_MAJOR == 6 && PROJ_VERSION_MINOR < 2
792
    /* PROJ lookups are faster with EPSG in uppercase. Fixed in PROJ 6.2 */
793
    /* Do that only for those versions, as it can create confusion if using */
794
    /* a real old-style 'epsg' file... */
795
    char szTemp[24];
796
    if (numargs && strncmp(args[0], "init=epsg:", strlen("init=epsg:")) == 0 &&
797
        strlen(args[0]) < 24) {
798
      strcpy(szTemp, "init=EPSG:");
799
      strcat(szTemp, args[0] + strlen("init=epsg:"));
800
      args[0] = szTemp;
801
    }
802
#endif
803

804
    args[numargs] = (char *)"type=crs";
29,599✔
805
    ++numargs;
29,599✔
806

807
#if 0
808
      for( int i = 0; i < numargs; i++ )
809
          fprintf(stderr, "%s ", args[i]);
810
      fprintf(stderr, "\n");
811
#endif
812
    if (!(p->proj = proj_create_argv(p->proj_ctx->proj_ctx, numargs, args))) {
29,599✔
813
      if (p->numargs > 1) {
19✔
814
        msSetError(MS_PROJERR, "PROJ error \"%s\" when instantiating \"%s:%s\"",
×
815
                   "msProcessProjection()", msProjGetProjErrorString(p),
816
                   p->args[0], p->args[1]);
×
817
      } else {
818
        msSetError(MS_PROJERR, "PROJ error \"%s\" when instantiating \"%s\"",
19✔
819
                   "msProcessProjection()", msProjGetProjErrorString(p),
820
                   p->args[0]);
19✔
821
      }
822
      free(args);
19✔
823
      return (-1);
19✔
824
    }
825
    free(args);
29,580✔
826
  }
827

828
#ifdef USE_PROJ_FASTPATHS
829
  if (strcasestr(p->args[0], "epsg:4326")) {
830
    p->wellknownprojection = wkp_lonlat;
831
  } else if (strcasestr(p->args[0], "epsg:3857")) {
832
    p->wellknownprojection = wkp_gmerc;
833
  } else {
834
    p->wellknownprojection = wkp_none;
835
  }
836
#endif
837

838
  return (0);
839
}
840

841
/************************************************************************/
842
/*                           int msIsAxisInverted                       */
843
/*      Check to see if we should invert the axis.                       */
844
/*                                                                      */
845
/************************************************************************/
846
int msIsAxisInverted(int epsg_code) {
3,515✔
847
  if (epsg_code < 0)
3,515✔
848
    return MS_FALSE;
849
  const unsigned int row = epsg_code / 8;
3,515✔
850
  const unsigned char index = epsg_code % 8;
3,515✔
851

852
  /*check the static table*/
853
  if ((row < sizeof(axisOrientationEpsgCodes)) &&
3,515✔
854
      (axisOrientationEpsgCodes[row] & (1 << index)))
3,497✔
855
    return MS_TRUE;
856
  else
857
    return MS_FALSE;
305✔
858
}
859

860
/************************************************************************/
861
/*                           msProjectPoint()                           */
862
/************************************************************************/
863
int msProjectPoint(projectionObj *in, projectionObj *out, pointObj *point) {
11✔
864
  int ret;
865
  reprojectionObj *reprojector = msProjectCreateReprojector(in, out);
11✔
866
  if (!reprojector)
11✔
867
    return MS_FAILURE;
868
  ret = msProjectPointEx(reprojector, point);
11✔
869
  msProjectDestroyReprojector(reprojector);
11✔
870
  return ret;
11✔
871
}
872

873
/************************************************************************/
874
/*                           msProjectPointEx()                         */
875
/************************************************************************/
876
int msProjectPointEx(reprojectionObj *reprojector, pointObj *point) {
3,312,924✔
877
  projectionObj *in = reprojector->in;
3,312,924✔
878
  projectionObj *out = reprojector->out;
3,312,924✔
879

880
  if (in && in->gt.need_geotransform) {
3,312,924✔
881
    double x_out, y_out;
882

883
    x_out = in->gt.geotransform[0] + in->gt.geotransform[1] * point->x +
167,755✔
884
            in->gt.geotransform[2] * point->y;
167,755✔
885
    y_out = in->gt.geotransform[3] + in->gt.geotransform[4] * point->x +
167,755✔
886
            in->gt.geotransform[5] * point->y;
167,755✔
887

888
    point->x = x_out;
167,755✔
889
    point->y = y_out;
167,755✔
890
  }
891

892
  if (reprojector->pj) {
3,312,924✔
893
    PJ_COORD c;
894
    c.xyzt.x = point->x;
3,299,985✔
895
    c.xyzt.y = point->y;
3,299,985✔
896
    c.xyzt.z = 0;
3,299,985✔
897
    c.xyzt.t = 0;
3,299,985✔
898
    c = proj_trans(reprojector->pj, PJ_FWD, c);
3,299,985✔
899
    if (c.xyzt.x == HUGE_VAL || c.xyzt.y == HUGE_VAL) {
3,299,985✔
900
      return MS_FAILURE;
9,080✔
901
    }
902
    point->x = c.xyzt.x;
3,290,905✔
903
    point->y = c.xyzt.y;
3,290,905✔
904
  }
905

906
  if (out && out->gt.need_geotransform) {
3,303,844✔
907
    double x_out, y_out;
908

909
    x_out = out->gt.invgeotransform[0] + out->gt.invgeotransform[1] * point->x +
24,957✔
910
            out->gt.invgeotransform[2] * point->y;
24,957✔
911
    y_out = out->gt.invgeotransform[3] + out->gt.invgeotransform[4] * point->x +
24,957✔
912
            out->gt.invgeotransform[5] * point->y;
24,957✔
913

914
    point->x = x_out;
24,957✔
915
    point->y = y_out;
24,957✔
916
  }
917

918
  return (MS_SUCCESS);
919
}
920

921
/************************************************************************/
922
/*                         msProjectGrowRect()                          */
923
/************************************************************************/
924
static void msProjectGrowRect(reprojectionObj *reprojector, rectObj *prj_rect,
×
925
                              pointObj *prj_point, int *failure)
926

927
{
928
  if (msProjectPointEx(reprojector, prj_point) == MS_SUCCESS) {
×
929
    prj_rect->miny = MS_MIN(prj_rect->miny, prj_point->y);
×
930
    prj_rect->maxy = MS_MAX(prj_rect->maxy, prj_point->y);
×
931
    prj_rect->minx = MS_MIN(prj_rect->minx, prj_point->x);
×
932
    prj_rect->maxx = MS_MAX(prj_rect->maxx, prj_point->x);
×
933
  } else
934
    (*failure)++;
×
935
}
×
936

937
/************************************************************************/
938
/*                          msProjectSegment()                          */
939
/*                                                                      */
940
/*      Interpolate along a line segment for which one end              */
941
/*      reprojects and the other end does not.  Finds the horizon.      */
942
/************************************************************************/
943
static int msProjectSegment(reprojectionObj *reprojector, pointObj *start,
185✔
944
                            pointObj *end)
945

946
{
947
  pointObj testPoint, subStart, subEnd;
948

949
  /* -------------------------------------------------------------------- */
950
  /*      Without loss of generality we assume the first point            */
951
  /*      reprojects, and the second doesn't.  If that is not the case    */
952
  /*      then re-call with the points reversed.                          */
953
  /* -------------------------------------------------------------------- */
954
  testPoint = *start;
185✔
955
  if (msProjectPointEx(reprojector, &testPoint) == MS_FAILURE) {
185✔
956
    testPoint = *end;
×
957
    if (msProjectPointEx(reprojector, &testPoint) == MS_FAILURE)
×
958
      return MS_FAILURE;
959
    else
960
      return msProjectSegment(reprojector, end, start);
×
961
  }
962

963
  /* -------------------------------------------------------------------- */
964
  /*      We will apply a binary search till we are within out            */
965
  /*      tolerance.                                                      */
966
  /* -------------------------------------------------------------------- */
967
  subStart = *start;
185✔
968
  subEnd = *end;
185✔
969

970
#define TOLERANCE 0.01
971

972
  while (fabs(subStart.x - subEnd.x) + fabs(subStart.y - subEnd.y) >
2,511✔
973
         TOLERANCE) {
974
    pointObj midPoint = {0};
975

976
    midPoint.x = (subStart.x + subEnd.x) * 0.5;
2,326✔
977
    midPoint.y = (subStart.y + subEnd.y) * 0.5;
2,326✔
978

979
    testPoint = midPoint;
2,326✔
980

981
    if (msProjectPointEx(reprojector, &testPoint) == MS_FAILURE) {
2,326✔
982
      if (midPoint.x == subEnd.x && midPoint.y == subEnd.y)
1,301✔
983
        return MS_FAILURE; /* break infinite loop */
984

985
      subEnd = midPoint;
986
    } else {
987
      if (midPoint.x == subStart.x && midPoint.y == subStart.y)
1,025✔
988
        return MS_FAILURE; /* break infinite loop */
989

990
      subStart = midPoint;
991
    }
992
  }
993

994
  /* -------------------------------------------------------------------- */
995
  /*      Now reproject the end points and return.                        */
996
  /* -------------------------------------------------------------------- */
997
  *end = subStart;
185✔
998

999
  if (msProjectPointEx(reprojector, end) == MS_FAILURE ||
370✔
1000
      msProjectPointEx(reprojector, start) == MS_FAILURE)
185✔
1001
    return MS_FAILURE;
×
1002
  else
1003
    return MS_SUCCESS;
1004
}
1005

1006
/************************************************************************/
1007
/*                   msProjectGetLineCuttingCase()                      */
1008
/************************************************************************/
1009

1010
/* Detect projecting from north polar stereographic to longlat or EPSG:3857 */
1011
/* or from lonlat lon_wrap = 0 */
1012
#ifdef USE_GEOS
1013
static msLineCuttingCase
1014
msProjectGetLineCuttingCase(reprojectionObj *reprojector) {
12,854✔
1015
  if (reprojector->lineCuttingCase != LINE_CUTTING_UNKNOWN)
12,854✔
1016
    return reprojector->lineCuttingCase;
1017

1018
  projectionObj *in = reprojector->in;
272✔
1019
  projectionObj *out = reprojector->out;
272✔
1020
  const double EPS = 1e-10;
1021

1022
  if (!in->gt.need_geotransform && msProjIsGeographicCRS(in)) {
272✔
1023
    int i;
1024
    for (i = 0; i < in->numargs; i++) {
235✔
1025
      if (strncmp(in->args[i], "lon_wrap=", strlen("lon_wrap=")) == 0 &&
122✔
1026
          atof(in->args[i] + strlen("lon_wrap=")) == 0.0) {
2✔
1027
        reprojector->lineCuttingCase = LINE_CUTTING_FROM_LONGLAT_WRAP0;
2✔
1028

1029
        msInitShape(&(reprojector->splitShape));
2✔
1030
        reprojector->splitShape.type = MS_SHAPE_POLYGON;
2✔
1031
        int j;
1032
        for (j = -1; j <= 1; j += 2) {
6✔
1033
          const double x = j * 180;
4✔
1034
          pointObj p = {0}; // initialize
4✔
1035
          lineObj newLine = {0, NULL};
4✔
1036
          p.x = x - EPS;
4✔
1037
          p.y = 90;
4✔
1038
          msAddPointToLine(&newLine, &p);
4✔
1039
          p.x = x + EPS;
4✔
1040
          p.y = 90;
4✔
1041
          msAddPointToLine(&newLine, &p);
4✔
1042
          p.x = x + EPS;
4✔
1043
          p.y = -90;
4✔
1044
          msAddPointToLine(&newLine, &p);
4✔
1045
          p.x = x - EPS;
4✔
1046
          p.y = -90;
4✔
1047
          msAddPointToLine(&newLine, &p);
4✔
1048
          p.x = x - EPS;
4✔
1049
          p.y = 90;
4✔
1050
          msAddPointToLine(&newLine, &p);
4✔
1051
          msAddLineDirectly(&(reprojector->splitShape), &newLine);
4✔
1052
        }
1053

1054
        return reprojector->lineCuttingCase;
2✔
1055
      }
1056
    }
1057
  }
1058

1059
  if (!(!in->gt.need_geotransform && !msProjIsGeographicCRS(in) &&
424✔
1060
        (msProjIsGeographicCRS(out) ||
154✔
1061
         (out->numargs == 1 && strcmp(out->args[0], "init=epsg:3857") == 0)))) {
17✔
1062
    reprojector->lineCuttingCase = LINE_CUTTING_NONE;
126✔
1063
    return reprojector->lineCuttingCase;
126✔
1064
  }
1065

1066
  int srcIsPolar;
1067
  double extremeLongEasting;
1068
  if (msProjIsGeographicCRS(out)) {
144✔
1069
    pointObj p;
1070
    double gt3 = out->gt.need_geotransform ? out->gt.geotransform[3] : 0.0;
137✔
1071
    double gt4 = out->gt.need_geotransform ? out->gt.geotransform[4] : 0.0;
137✔
1072
    double gt5 = out->gt.need_geotransform ? out->gt.geotransform[5] : 1.0;
137✔
1073
    p.x = 0.0;
137✔
1074
    p.y = 0.0;
137✔
1075
    srcIsPolar = msProjectPointEx(reprojector, &p) == MS_SUCCESS &&
137✔
1076
                 fabs(gt3 + p.x * gt4 + p.y * gt5 - 90) < 1e-8;
137✔
1077
    extremeLongEasting = 180;
1078
  } else {
1079
    pointObj p1;
1080
    pointObj p2;
1081
    double gt1 = out->gt.need_geotransform ? out->gt.geotransform[1] : 1.0;
7✔
1082
    p1.x = 0.0;
7✔
1083
    p1.y = -0.1;
7✔
1084
    p2.x = 0.0;
7✔
1085
    p2.y = 0.1;
7✔
1086
    srcIsPolar = msProjectPointEx(reprojector, &p1) == MS_SUCCESS &&
14✔
1087
                 msProjectPointEx(reprojector, &p2) == MS_SUCCESS &&
7✔
1088
                 fabs((p1.x - p2.x) * gt1) > 20e6;
7✔
1089
    extremeLongEasting = 20037508.3427892;
1090
  }
1091
  if (!srcIsPolar) {
1092
    reprojector->lineCuttingCase = LINE_CUTTING_NONE;
142✔
1093
    return reprojector->lineCuttingCase;
142✔
1094
  }
1095

1096
  pointObj p = {0}; // initialize
2✔
1097
  double invgt0 = out->gt.need_geotransform ? out->gt.invgeotransform[0] : 0.0;
2✔
1098
  double invgt1 = out->gt.need_geotransform ? out->gt.invgeotransform[1] : 1.0;
2✔
1099
  double invgt3 = out->gt.need_geotransform ? out->gt.invgeotransform[3] : 0.0;
2✔
1100
  double invgt4 = out->gt.need_geotransform ? out->gt.invgeotransform[4] : 0.0;
2✔
1101

1102
  lineObj newLine = {0, NULL};
2✔
1103

1104
  p.x = invgt0 + -extremeLongEasting * (1 - EPS) * invgt1;
2✔
1105
  p.y = invgt3 + -extremeLongEasting * (1 - EPS) * invgt4;
2✔
1106
  /* coverity[swapped_arguments] */
1107
  msProjectPoint(out, in, &p);
2✔
1108
  pointObj first = p;
2✔
1109
  msAddPointToLine(&newLine, &p);
2✔
1110

1111
  p.x = invgt0 + extremeLongEasting * (1 - EPS) * invgt1;
2✔
1112
  p.y = invgt3 + extremeLongEasting * (1 - EPS) * invgt4;
2✔
1113
  /* coverity[swapped_arguments] */
1114
  msProjectPoint(out, in, &p);
2✔
1115
  msAddPointToLine(&newLine, &p);
2✔
1116

1117
  p.x = 0;
2✔
1118
  p.y = 0;
2✔
1119
  msAddPointToLine(&newLine, &p);
2✔
1120

1121
  msAddPointToLine(&newLine, &first);
2✔
1122

1123
  msInitShape(&(reprojector->splitShape));
2✔
1124
  reprojector->splitShape.type = MS_SHAPE_POLYGON;
2✔
1125
  msAddLineDirectly(&(reprojector->splitShape), &newLine);
2✔
1126

1127
  reprojector->lineCuttingCase = LINE_CUTTING_FROM_POLAR;
2✔
1128
  return reprojector->lineCuttingCase;
2✔
1129
}
1130
#endif
1131

1132
/************************************************************************/
1133
/*                msProjectIsReprojectorStillValid()                    */
1134
/************************************************************************/
1135

1136
int msProjectIsReprojectorStillValid(reprojectionObj *reprojector) {
17,149✔
1137
  if (reprojector->in &&
17,149✔
1138
      reprojector->in->generation_number != reprojector->generation_number_in)
17,149✔
1139
    return MS_FALSE;
1140
  if (reprojector->out &&
17,149✔
1141
      reprojector->out->generation_number != reprojector->generation_number_out)
17,149✔
1142
    return MS_FALSE;
2✔
1143
  return MS_TRUE;
1144
}
1145

1146
/************************************************************************/
1147
/*                         msProjectShapeLine()                         */
1148
/*                                                                      */
1149
/*      Reproject a single linestring, potentially splitting into       */
1150
/*      more than one line string if there are over the horizon         */
1151
/*      portions.                                                       */
1152
/*                                                                      */
1153
/*      For polygons, no splitting takes place, but over the horizon    */
1154
/*      points are clipped, and one segment is run from the fall        */
1155
/*      over the horizon point to the come back over the horizon point. */
1156
/************************************************************************/
1157

1158
static int msProjectShapeLine(reprojectionObj *reprojector, shapeObj *shape,
15,744✔
1159
                              int line_index)
1160

1161
{
1162
  int i;
1163
  pointObj lastPoint, thisPoint, wrkPoint;
1164
  lineObj *line = shape->line + line_index;
15,744✔
1165
  lineObj *line_out = line;
1166
  int valid_flag = 0; /* 1=true, -1=false, 0=unknown */
1167
  int numpoints_in = line->numpoints;
15,744✔
1168
  int line_alloc = numpoints_in;
1169
  int wrap_test;
1170
  projectionObj *in = reprojector->in;
15,744✔
1171
  projectionObj *out = reprojector->out;
15,744✔
1172

1173
#ifdef USE_PROJ_FASTPATHS
1174
#define MAXEXTENT 20037508.34
1175
#define M_PIby360 .0087266462599716479
1176
#define MAXEXTENTby180 111319.4907777777777777777
1177
#define p_x line->point[i].x
1178
#define p_y line->point[i].y
1179
  if (in->wellknownprojection == wkp_lonlat &&
1180
      out->wellknownprojection == wkp_gmerc) {
1181
    for (i = line->numpoints - 1; i >= 0; i--) {
1182
      p_x *= MAXEXTENTby180;
1183
      p_y = log(tan((90 + p_y) * M_PIby360)) * MS_RAD_TO_DEG;
1184
      p_y *= MAXEXTENTby180;
1185
      if (p_x > MAXEXTENT)
1186
        p_x = MAXEXTENT;
1187
      if (p_x < -MAXEXTENT)
1188
        p_x = -MAXEXTENT;
1189
      if (p_y > MAXEXTENT)
1190
        p_y = MAXEXTENT;
1191
      if (p_y < -MAXEXTENT)
1192
        p_y = -MAXEXTENT;
1193
    }
1194
    return MS_SUCCESS;
1195
  }
1196
  if (in->wellknownprojection == wkp_gmerc &&
1197
      out->wellknownprojection == wkp_lonlat) {
1198
    for (i = line->numpoints - 1; i >= 0; i--) {
1199
      if (p_x > MAXEXTENT)
1200
        p_x = MAXEXTENT;
1201
      else if (p_x < -MAXEXTENT)
1202
        p_x = -MAXEXTENT;
1203
      if (p_y > MAXEXTENT)
1204
        p_y = MAXEXTENT;
1205
      else if (p_y < -MAXEXTENT)
1206
        p_y = -MAXEXTENT;
1207
      p_x = (p_x / MAXEXTENT) * 180;
1208
      p_y = (p_y / MAXEXTENT) * 180;
1209
      p_y = MS_RAD_TO_DEG * (2 * atan(exp(p_y * MS_DEG_TO_RAD)) - MS_PI2);
1210
    }
1211
    msComputeBounds(shape); /* fixes bug 1586 */
1212
    return MS_SUCCESS;
1213
  }
1214
#undef p_x
1215
#undef p_y
1216
#endif
1217

1218
#ifdef USE_GEOS
1219
  int use_splitShape = MS_FALSE;
1220
  int use_splitShape_check_intersects = MS_FALSE;
1221
  if (shape->type == MS_SHAPE_LINE &&
22,172✔
1222
      msProjectGetLineCuttingCase(reprojector) == LINE_CUTTING_FROM_POLAR) {
6,428✔
1223
    use_splitShape = MS_TRUE;
1224
    use_splitShape_check_intersects = MS_TRUE;
1225
  } else if (shape->type == MS_SHAPE_LINE &&
22,168✔
1226
             msProjectGetLineCuttingCase(reprojector) ==
6,426✔
1227
                 LINE_CUTTING_FROM_LONGLAT_WRAP0) {
1228
    for (i = 0; i < numpoints_in; i++) {
4✔
1229
      if (fabs(line->point[i].x) > 180) {
4✔
1230
        use_splitShape = MS_TRUE;
1231
        break;
1232
      }
1233
    }
1234
  }
1235

1236
  if (use_splitShape) {
2✔
1237
    shapeObj tmpShapeInputLine;
1238
    msInitShape(&tmpShapeInputLine);
4✔
1239
    tmpShapeInputLine.type = MS_SHAPE_LINE;
4✔
1240
    tmpShapeInputLine.numlines = 1;
4✔
1241
    tmpShapeInputLine.line = line;
4✔
1242

1243
    shapeObj *diff = NULL;
1244
    if (use_splitShape_check_intersects == MS_FALSE ||
6✔
1245
        msGEOSIntersects(&tmpShapeInputLine, &(reprojector->splitShape))) {
2✔
1246
      diff = msGEOSDifference(&tmpShapeInputLine, &(reprojector->splitShape));
4✔
1247
    }
1248

1249
    tmpShapeInputLine.numlines = 0;
4✔
1250
    tmpShapeInputLine.line = NULL;
4✔
1251
    msFreeShape(&tmpShapeInputLine);
4✔
1252

1253
    if (diff) {
4✔
1254
      for (int j = 0; j < diff->numlines; j++) {
12✔
1255
        for (i = 0; i < diff->line[j].numpoints; i++) {
24✔
1256
          msProjectPointEx(reprojector, &(diff->line[j].point[i]));
16✔
1257
        }
1258
        if (j == 0) {
8✔
1259
          line_out->numpoints = diff->line[j].numpoints;
4✔
1260
          memcpy(line_out->point, diff->line[0].point,
4✔
1261
                 sizeof(pointObj) * line_out->numpoints);
4✔
1262
        } else {
1263
          msAddLineDirectly(shape, &(diff->line[j]));
4✔
1264
        }
1265
      }
1266
      msFreeShape(diff);
4✔
1267
      msFree(diff);
4✔
1268
      return MS_SUCCESS;
4✔
1269
    }
1270
  }
1271
#endif
1272

1273
  wrap_test = out != NULL && out->proj != NULL && msProjIsGeographicCRS(out) &&
27,818✔
1274
              !msProjIsGeographicCRS(in);
12,078✔
1275

1276
  line->numpoints = 0;
15,740✔
1277

1278
  memset(&lastPoint, 0, sizeof(lastPoint));
1279

1280
  /* -------------------------------------------------------------------- */
1281
  /*      Loop over all input points in linestring.                       */
1282
  /* -------------------------------------------------------------------- */
1283
  for (i = 0; i < numpoints_in; i++) {
3,301,694✔
1284
    int ms_err;
1285
    wrkPoint = thisPoint = line->point[i];
3,285,954✔
1286

1287
    ms_err = msProjectPointEx(reprojector, &wrkPoint);
3,285,954✔
1288

1289
    /* -------------------------------------------------------------------- */
1290
    /*      Apply wrap logic.                                               */
1291
    /* -------------------------------------------------------------------- */
1292
    if (wrap_test && i > 0 && ms_err != MS_FAILURE) {
3,285,954✔
1293
      double dist;
1294
      pointObj pt1Geo;
1295

1296
      if (line_out->numpoints > 0)
1,256,090✔
1297
        pt1Geo = line_out->point[line_out->numpoints - 1];
1,256,084✔
1298
      else
1299
        pt1Geo = wrkPoint; /* this is a cop out */
6✔
1300

1301
      if (out->gt.need_geotransform && out->gt.geotransform[2] == 0) {
1,256,090✔
1302
        dist = out->gt.geotransform[1] * (wrkPoint.x - pt1Geo.x);
1,711✔
1303
      } else {
1304
        dist = wrkPoint.x - pt1Geo.x;
1,254,379✔
1305
      }
1306

1307
      if (fabs(dist) > 180.0 &&
1,256,763✔
1308
          msTestNeedWrap(thisPoint, lastPoint, pt1Geo, reprojector)) {
673✔
1309
        if (out->gt.need_geotransform && out->gt.geotransform[2] == 0) {
664✔
1310
          if (dist > 0.0)
×
1311
            wrkPoint.x -= 360.0 * out->gt.invgeotransform[1];
×
1312
          else if (dist < 0.0)
×
1313
            wrkPoint.x += 360.0 * out->gt.invgeotransform[1];
×
1314
        } else {
1315
          if (dist > 0.0)
664✔
1316
            wrkPoint.x -= 360.0;
×
1317
          else if (dist < 0.0)
664✔
1318
            wrkPoint.x += 360.0;
664✔
1319
        }
1320
      }
1321
    }
1322

1323
    /* -------------------------------------------------------------------- */
1324
    /*      Put result into output line with appropriate logic for          */
1325
    /*      failure breaking lines, etc.                                    */
1326
    /* -------------------------------------------------------------------- */
1327
    if (ms_err == MS_FAILURE) {
3,284,196✔
1328
      /* We have started out invalid */
1329
      if (i == 0) {
7,755✔
1330
        valid_flag = -1;
1331
      }
1332

1333
      /* valid data has ended, we need to work out the horizon */
1334
      else if (valid_flag == 1) {
7,598✔
1335
        pointObj startPoint, endPoint;
1336

1337
        startPoint = lastPoint;
84✔
1338
        endPoint = thisPoint;
84✔
1339
        if (msProjectSegment(reprojector, &startPoint, &endPoint) ==
84✔
1340
            MS_SUCCESS) {
1341
          line_out->point[line_out->numpoints++] = endPoint;
84✔
1342
        }
1343
        valid_flag = -1;
1344
      }
1345

1346
      /* Still invalid ... */
1347
      else if (valid_flag == -1) {
1348
        /* do nothing */
1349
      }
1350
    }
1351

1352
    else {
1353
      /* starting out valid. */
1354
      if (i == 0) {
3,278,199✔
1355
        line_out->point[line_out->numpoints++] = wrkPoint;
15,583✔
1356
        valid_flag = 1;
1357
      }
1358

1359
      /* Still valid, nothing special */
1360
      else if (valid_flag == 1) {
3,262,616✔
1361
        line_out->point[line_out->numpoints++] = wrkPoint;
3,262,515✔
1362
      }
1363

1364
      /* we have come over the horizon, figure out where, start newline*/
1365
      else {
1366
        pointObj startPoint, endPoint;
1367

1368
        startPoint = lastPoint;
101✔
1369
        endPoint = thisPoint;
101✔
1370
        if (msProjectSegment(reprojector, &endPoint, &startPoint) ==
101✔
1371
            MS_SUCCESS) {
1372
          lineObj newLine;
1373

1374
          /* force pre-allocation of lots of points room */
1375
          if (line_out->numpoints > 0 && shape->type == MS_SHAPE_LINE) {
101✔
1376
            newLine.numpoints = numpoints_in - i + 1;
1✔
1377
            newLine.point = line->point;
1✔
1378
            msAddLine(shape, &newLine);
1✔
1379

1380
            /* new line is now lineout, but start without any points */
1381
            line_out = shape->line + shape->numlines - 1;
1✔
1382

1383
            line_out->numpoints = 0;
1✔
1384

1385
            /* the shape->line array is realloc, refetch "line" */
1386
            line = shape->line + line_index;
1✔
1387
          } else if (line_out == line && line->numpoints >= i - 2) {
100✔
1388
            newLine.numpoints = numpoints_in;
5✔
1389
            newLine.point = line->point;
5✔
1390
            msAddLine(shape, &newLine);
5✔
1391

1392
            line = shape->line + line_index;
5✔
1393

1394
            line_out = shape->line + shape->numlines - 1;
5✔
1395
            line_out->numpoints = line->numpoints;
5✔
1396
            line->numpoints = 0;
5✔
1397

1398
            /*
1399
             * Now realloc this array large enough to hold all
1400
             * the points we could possibly need to add.
1401
             */
1402
            line_alloc = line_alloc * 2;
5✔
1403

1404
            line_out->point = (pointObj *)realloc(
5✔
1405
                line_out->point, sizeof(pointObj) * line_alloc);
5✔
1406
          }
1407

1408
          line_out->point[line_out->numpoints++] = startPoint;
101✔
1409
        }
1410
        line_out->point[line_out->numpoints++] = wrkPoint;
101✔
1411
        valid_flag = 1;
1412
      }
1413
    }
1414

1415
    lastPoint = thisPoint;
3,285,954✔
1416
  }
1417

1418
  /* -------------------------------------------------------------------- */
1419
  /*      Make sure that polygons are closed, even if the trip over       */
1420
  /*      the horizon left them unclosed.                                 */
1421
  /* -------------------------------------------------------------------- */
1422
  if (shape->type == MS_SHAPE_POLYGON && line_out->numpoints > 2 &&
15,740✔
1423
      (line_out->point[0].x != line_out->point[line_out->numpoints - 1].x ||
9,312✔
1424
       line_out->point[0].y != line_out->point[line_out->numpoints - 1].y)) {
4,624✔
1425
    /* make a copy because msAddPointToLine can realloc the array */
1426
    pointObj sFirstPoint = line_out->point[0];
4,698✔
1427
    msAddPointToLine(line_out, &sFirstPoint);
4,698✔
1428
  }
1429

1430
  return (MS_SUCCESS);
1431
}
1432

1433
/************************************************************************/
1434
/*                           msProjectShape()                           */
1435
/************************************************************************/
1436
int msProjectShape(projectionObj *in, projectionObj *out, shapeObj *shape) {
212✔
1437
  int ret;
1438
  reprojectionObj *reprojector = msProjectCreateReprojector(in, out);
212✔
1439
  if (!reprojector)
212✔
1440
    return MS_FAILURE;
1441
  ret = msProjectShapeEx(reprojector, shape);
212✔
1442
  msProjectDestroyReprojector(reprojector);
212✔
1443
  return ret;
212✔
1444
}
1445

1446
/************************************************************************/
1447
/*                          msProjectShapeEx()                          */
1448
/************************************************************************/
1449
int msProjectShapeEx(reprojectionObj *reprojector, shapeObj *shape) {
24,905✔
1450
  int i;
1451
#ifdef USE_PROJ_FASTPATHS
1452
  int j;
1453

1454
#define p_x shape->line[i].point[j].x
1455
#define p_y shape->line[i].point[j].y
1456
  if (in->wellknownprojection == wkp_lonlat &&
1457
      out->wellknownprojection == wkp_gmerc) {
1458
    for (i = shape->numlines - 1; i >= 0; i--) {
1459
      for (j = shape->line[i].numpoints - 1; j >= 0; j--) {
1460
        p_x *= MAXEXTENTby180;
1461
        p_y = log(tan((90 + p_y) * M_PIby360)) * MS_RAD_TO_DEG;
1462
        p_y *= MAXEXTENTby180;
1463
        if (p_x > MAXEXTENT)
1464
          p_x = MAXEXTENT;
1465
        else if (p_x < -MAXEXTENT)
1466
          p_x = -MAXEXTENT;
1467
        if (p_y > MAXEXTENT)
1468
          p_y = MAXEXTENT;
1469
        else if (p_y < -MAXEXTENT)
1470
          p_y = -MAXEXTENT;
1471
      }
1472
    }
1473
    msComputeBounds(shape); /* fixes bug 1586 */
1474
    return MS_SUCCESS;
1475
  }
1476
  if (in->wellknownprojection == wkp_gmerc &&
1477
      out->wellknownprojection == wkp_lonlat) {
1478
    for (i = shape->numlines - 1; i >= 0; i--) {
1479
      for (j = shape->line[i].numpoints - 1; j >= 0; j--) {
1480
        if (p_x > MAXEXTENT)
1481
          p_x = MAXEXTENT;
1482
        else if (p_x < -MAXEXTENT)
1483
          p_x = -MAXEXTENT;
1484
        if (p_y > MAXEXTENT)
1485
          p_y = MAXEXTENT;
1486
        else if (p_y < -MAXEXTENT)
1487
          p_y = -MAXEXTENT;
1488
        p_x = (p_x / MAXEXTENT) * 180;
1489
        p_y = (p_y / MAXEXTENT) * 180;
1490
        p_y = MS_RAD_TO_DEG * (2 * atan(exp(p_y * MS_DEG_TO_RAD)) - MS_PI2);
1491
      }
1492
    }
1493
    msComputeBounds(shape); /* fixes bug 1586 */
1494
    return MS_SUCCESS;
1495
  }
1496
#undef p_x
1497
#undef p_y
1498
#endif
1499

1500
  if (shape->numlines == 0) {
24,905✔
1501
    // don't attempt to project any NULL geometries
1502
    // but if we want to return the record's attributes we won't free the shape
1503
    // and throw an error
1504
    shape->type = MS_SHAPE_NULL;
1✔
1505
    return MS_SUCCESS;
1✔
1506
  } else {
1507
    for (i = shape->numlines - 1; i >= 0; i--) {
50,017✔
1508
      if (shape->type == MS_SHAPE_LINE || shape->type == MS_SHAPE_POLYGON) {
25,113✔
1509
        if (msProjectShapeLine(reprojector, shape, i) == MS_FAILURE)
11,045✔
1510
          msShapeDeleteLine(shape, i);
×
1511
      } else if (msProjectLineEx(reprojector, shape->line + i) == MS_FAILURE) {
14,068✔
1512
        msShapeDeleteLine(shape, i);
×
1513
      }
1514
    }
1515

1516
    if (shape->numlines == 0) {
24,904✔
1517
      msFreeShape(shape);
×
1518
      return MS_FAILURE;
×
1519
    } else {
1520
      msComputeBounds(shape); /* fixes bug 1586 */
24,904✔
1521
      return (MS_SUCCESS);
24,904✔
1522
    }
1523
  }
1524
}
1525

1526
/************************************************************************/
1527
/*                           msProjectLine()                            */
1528
/************************************************************************/
1529
int msProjectLine(projectionObj *in, projectionObj *out, lineObj *line) {
28✔
1530
  int ret;
1531
  reprojectionObj *reprojector = msProjectCreateReprojector(in, out);
28✔
1532
  if (!reprojector)
28✔
1533
    return MS_FAILURE;
1534
  ret = msProjectLineEx(reprojector, line);
28✔
1535
  msProjectDestroyReprojector(reprojector);
28✔
1536
  return ret;
28✔
1537
}
1538

1539
/************************************************************************/
1540
/*                         msProjectLineEx()                            */
1541
/*                                                                      */
1542
/*      This function is now normally only used for point data.         */
1543
/*      msProjectShapeLine() is used for lines and polygons and has     */
1544
/*      lots of logic to handle horizon crossing.                       */
1545
/************************************************************************/
1546

1547
int msProjectLineEx(reprojectionObj *reprojector, lineObj *line) {
14,096✔
1548
  int be_careful = reprojector->out->proj != NULL &&
14,096✔
1549
                   msProjIsGeographicCRS(reprojector->out) &&
19,750✔
1550
                   !msProjIsGeographicCRS(reprojector->in);
5,654✔
1551

1552
  if (be_careful) {
1553
    pointObj startPoint, thisPoint; /* locations in projected space */
1554

1555
    startPoint = line->point[0];
3,306✔
1556

1557
    for (int i = 0; i < line->numpoints; i++) {
8,379✔
1558
      double dist;
1559

1560
      thisPoint = line->point[i];
5,073✔
1561

1562
      /*
1563
      ** Read comments before msTestNeedWrap() to better understand
1564
      ** this dateline wrapping logic.
1565
      */
1566
      msProjectPointEx(reprojector, &(line->point[i]));
5,073✔
1567
      if (i > 0) {
5,073✔
1568
        dist = line->point[i].x - line->point[0].x;
1,767✔
1569
        if (fabs(dist) > 180.0) {
1,767✔
1570
          if (msTestNeedWrap(thisPoint, startPoint, line->point[0],
12✔
1571
                             reprojector)) {
1572
            if (dist > 0.0) {
×
1573
              line->point[i].x -= 360.0;
×
1574
            } else if (dist < 0.0) {
×
1575
              line->point[i].x += 360.0;
×
1576
            }
1577
          }
1578
        }
1579
      }
1580
    }
1581
  } else {
1582
    for (int i = 0; i < line->numpoints; i++) {
21,612✔
1583
      if (msProjectPointEx(reprojector, &(line->point[i])) == MS_FAILURE)
10,822✔
1584
        return MS_FAILURE;
1585
    }
1586
  }
1587

1588
  return (MS_SUCCESS);
1589
}
1590

1591
/************************************************************************/
1592
/*                           msProjectRectGrid()                        */
1593
/************************************************************************/
1594

1595
#define NUMBER_OF_SAMPLE_POINTS 100
1596

1597
static int msProjectRectGrid(reprojectionObj *reprojector, rectObj *rect) {
×
1598
  pointObj prj_point;
1599
  rectObj prj_rect;
1600
  int failure = 0;
×
1601
  int ix, iy;
1602

1603
  double dx, dy;
1604
  double x, y;
1605

1606
  prj_rect.minx = DBL_MAX;
×
1607
  prj_rect.miny = DBL_MAX;
×
1608
  prj_rect.maxx = -DBL_MAX;
×
1609
  prj_rect.maxy = -DBL_MAX;
×
1610

1611
  dx = (rect->maxx - rect->minx) / NUMBER_OF_SAMPLE_POINTS;
×
1612
  dy = (rect->maxy - rect->miny) / NUMBER_OF_SAMPLE_POINTS;
×
1613

1614
  /* first ensure the top left corner is processed, even if the rect
1615
     turns out to be degenerate. */
1616

1617
  prj_point.x = rect->minx;
×
1618
  prj_point.y = rect->miny;
×
1619
  prj_point.z = 0.0;
×
1620
  prj_point.m = 0.0;
×
1621

1622
  msProjectGrowRect(reprojector, &prj_rect, &prj_point, &failure);
×
1623

1624
  failure = 0;
×
1625
  for (ix = 0; ix <= NUMBER_OF_SAMPLE_POINTS; ix++) {
×
1626
    x = rect->minx + ix * dx;
×
1627

1628
    for (iy = 0; iy <= NUMBER_OF_SAMPLE_POINTS; iy++) {
×
1629
      y = rect->miny + iy * dy;
×
1630

1631
      prj_point.x = x;
×
1632
      prj_point.y = y;
×
1633
      msProjectGrowRect(reprojector, &prj_rect, &prj_point, &failure);
×
1634
    }
1635
  }
1636

1637
  if (prj_rect.minx > prj_rect.maxx) {
×
1638
    rect->minx = 0;
×
1639
    rect->maxx = 0;
×
1640
    rect->miny = 0;
×
1641
    rect->maxy = 0;
×
1642

1643
    msSetError(MS_PROJERR, "All points failed to reproject.",
×
1644
               "msProjectRect()");
1645
    return MS_FAILURE;
×
1646
  }
1647

1648
  if (failure) {
×
1649
    msDebug("msProjectRect(): some points failed to reproject, doing internal "
×
1650
            "sampling.\n");
1651
  }
1652

1653
  rect->minx = prj_rect.minx;
×
1654
  rect->miny = prj_rect.miny;
×
1655
  rect->maxx = prj_rect.maxx;
×
1656
  rect->maxy = prj_rect.maxy;
×
1657

1658
  return (MS_SUCCESS);
×
1659
}
1660

1661
/************************************************************************/
1662
/*                       msProjectRectAsPolygon()                       */
1663
/************************************************************************/
1664

1665
int msProjectRectAsPolygon(reprojectionObj *reprojector, rectObj *rect) {
4,774✔
1666
  shapeObj polygonObj;
1667
  lineObj ring;
1668
  /*  pointObj ringPoints[NUMBER_OF_SAMPLE_POINTS*4+4]; */
1669
  pointObj *ringPoints;
1670
  int ix, iy, ixy;
1671

1672
  double dx, dy;
1673

1674
  /* If projecting from longlat to projected */
1675
  /* This hack was introduced for WFS 2.0 compliance testing, but is far */
1676
  /* from being perfect */
1677
  if (reprojector->out && !msProjIsGeographicCRS(reprojector->out) &&
4,774✔
1678
      reprojector->in && msProjIsGeographicCRS(reprojector->in) &&
2,102✔
1679
      fabs(rect->minx - -180.0) < 1e-5 && fabs(rect->miny - -90.0) < 1e-5 &&
981✔
1680
      fabs(rect->maxx - 180.0) < 1e-5 && fabs(rect->maxy - 90.0) < 1e-5) {
10✔
1681
    pointObj pointTest;
1682
    pointTest.x = -180;
8✔
1683
    pointTest.y = 85;
8✔
1684
    msProjectPointEx(reprojector, &pointTest);
8✔
1685
    /* Detect if we are reprojecting from EPSG:4326 to EPSG:3857 */
1686
    /* and if so use more plausible bounds to avoid issues with computed */
1687
    /* resolution for WCS */
1688
    if (fabs(pointTest.x - -20037508.3427892) < 1e-5 &&
8✔
1689
        fabs(pointTest.y - 19971868.8804086)) {
2✔
1690
      rect->minx = -20037508.3427892;
2✔
1691
      rect->miny = -20037508.3427892;
2✔
1692
      rect->maxx = 20037508.3427892;
2✔
1693
      rect->maxy = 20037508.3427892;
2✔
1694
    } else {
1695
      rect->minx = -1e15;
6✔
1696
      rect->miny = -1e15;
6✔
1697
      rect->maxx = 1e15;
6✔
1698
      rect->maxy = 1e15;
6✔
1699
    }
1700
    return MS_SUCCESS;
1701
  }
1702

1703
  /* -------------------------------------------------------------------- */
1704
  /*      Build polygon as steps around the source rectangle              */
1705
  /*      and possibly its diagonal.                                      */
1706
  /* -------------------------------------------------------------------- */
1707
  dx = (rect->maxx - rect->minx) / NUMBER_OF_SAMPLE_POINTS;
4,766✔
1708
  dy = (rect->maxy - rect->miny) / NUMBER_OF_SAMPLE_POINTS;
4,766✔
1709

1710
  if (dx == 0 && dy == 0) {
4,766✔
1711
    pointObj foo;
1712
    msDebug("msProjectRect(): Warning: degenerate rect {%f,%f,%f,%f}\n",
67✔
1713
            rect->minx, rect->miny, rect->minx, rect->miny);
1714
    foo.x = rect->minx;
67✔
1715
    foo.y = rect->miny;
67✔
1716
    msProjectPointEx(reprojector, &foo);
67✔
1717
    rect->minx = rect->maxx = foo.x;
67✔
1718
    rect->miny = rect->maxy = foo.y;
67✔
1719
    return MS_SUCCESS;
1720
  }
1721

1722
  /* If there is more than two sample points we will also get samples from the
1723
   * diagonal line */
1724
  ringPoints =
1725
      (pointObj *)calloc(NUMBER_OF_SAMPLE_POINTS * 5 + 3, sizeof(pointObj));
4,699✔
1726
  ring.point = ringPoints;
4,699✔
1727
  ring.numpoints = 0;
4,699✔
1728

1729
  msInitShape(&polygonObj);
4,699✔
1730
  polygonObj.type = MS_SHAPE_POLYGON;
4,699✔
1731

1732
  /* sample along top */
1733
  if (dx != 0) {
4,699✔
1734
    for (ix = 0; ix <= NUMBER_OF_SAMPLE_POINTS; ix++) {
479,298✔
1735
      ringPoints[ring.numpoints].x = rect->minx + ix * dx;
474,599✔
1736
      ringPoints[ring.numpoints++].y = rect->miny;
474,599✔
1737
    }
1738
  }
1739

1740
  /* sample along right side */
1741
  if (dy != 0) {
4,699✔
1742
    for (iy = 1; iy <= NUMBER_OF_SAMPLE_POINTS; iy++) {
474,498✔
1743
      ringPoints[ring.numpoints].x = rect->maxx;
469,800✔
1744
      ringPoints[ring.numpoints++].y = rect->miny + iy * dy;
469,800✔
1745
    }
1746
  }
1747

1748
  /* sample along bottom */
1749
  if (dx != 0) {
4,699✔
1750
    for (ix = NUMBER_OF_SAMPLE_POINTS - 1; ix >= 0; ix--) {
474,599✔
1751
      ringPoints[ring.numpoints].x = rect->minx + ix * dx;
469,900✔
1752
      ringPoints[ring.numpoints++].y = rect->maxy;
469,900✔
1753
    }
1754
  }
1755

1756
  /* sample along left side */
1757
  if (dy != 0) {
4,699✔
1758
    for (iy = NUMBER_OF_SAMPLE_POINTS - 1; iy >= 0; iy--) {
474,498✔
1759
      ringPoints[ring.numpoints].x = rect->minx;
469,800✔
1760
      ringPoints[ring.numpoints++].y = rect->miny + iy * dy;
469,800✔
1761
    }
1762
  }
1763

1764
  /* sample along diagonal line */
1765
  /* This is done to handle cases where reprojection from world covering
1766
   * projection to one */
1767
  /* which isn't could cause min and max values of the projected rectangle to be
1768
   * invalid */
1769
  if (dy != 0 && dx != 0) {
4,699✔
1770
    /* No need to compute corners as they've already been computed */
1771
    for (ixy = NUMBER_OF_SAMPLE_POINTS - 2; ixy >= 1; ixy--) {
465,102✔
1772
      ringPoints[ring.numpoints].x = rect->minx + ixy * dx;
460,404✔
1773
      ringPoints[ring.numpoints++].y = rect->miny + ixy * dy;
460,404✔
1774
    }
1775
  }
1776

1777
  msAddLineDirectly(&polygonObj, &ring);
4,699✔
1778

1779
#ifdef notdef
1780
  FILE *wkt = fopen("/tmp/www-before.wkt", "w");
1781
  char *tmp = msShapeToWKT(&polygonObj);
1782
  fprintf(wkt, "%s\n", tmp);
1783
  free(tmp);
1784
  fclose(wkt);
1785
#endif
1786

1787
  /* -------------------------------------------------------------------- */
1788
  /*      Attempt to reproject.                                           */
1789
  /* -------------------------------------------------------------------- */
1790
  msProjectShapeLine(reprojector, &polygonObj, 0);
4,699✔
1791

1792
  /* If no points reprojected, try a grid sampling */
1793
  if (polygonObj.numlines == 0 || polygonObj.line[0].numpoints == 0) {
4,699✔
1794
    msFreeShape(&polygonObj);
×
1795
    return msProjectRectGrid(reprojector, rect);
×
1796
  }
1797

1798
#ifdef notdef
1799
  wkt = fopen("/tmp/www-after.wkt", "w");
1800
  tmp = msShapeToWKT(&polygonObj);
1801
  fprintf(wkt, "%s\n", tmp);
1802
  free(tmp);
1803
  fclose(wkt);
1804
#endif
1805

1806
  /* -------------------------------------------------------------------- */
1807
  /*      Collect bounds.                                                 */
1808
  /* -------------------------------------------------------------------- */
1809
  rect->minx = rect->maxx = polygonObj.line[0].point[0].x;
4,699✔
1810
  rect->miny = rect->maxy = polygonObj.line[0].point[0].y;
4,699✔
1811

1812
  for (ix = 1; ix < polygonObj.line[0].numpoints; ix++) {
2,341,901✔
1813
    pointObj *pnt = polygonObj.line[0].point + ix;
2,337,202✔
1814

1815
    rect->minx = MS_MIN(rect->minx, pnt->x);
2,337,202✔
1816
    rect->maxx = MS_MAX(rect->maxx, pnt->x);
2,337,202✔
1817
    rect->miny = MS_MIN(rect->miny, pnt->y);
2,337,202✔
1818
    rect->maxy = MS_MAX(rect->maxy, pnt->y);
3,443,543✔
1819
  }
1820

1821
  msFreeShape(&polygonObj);
4,699✔
1822

1823
  /* -------------------------------------------------------------------- */
1824
  /*      Special case to handle reprojection from "more than the         */
1825
  /*      whole world" projected coordinates that sometimes produce a     */
1826
  /*      region greater than 360 degrees wide due to various wrapping    */
1827
  /*      logic.                                                          */
1828
  /* -------------------------------------------------------------------- */
1829
  if (reprojector->out && msProjIsGeographicCRS(reprojector->out) &&
4,699✔
1830
      reprojector->in && !msProjIsGeographicCRS(reprojector->in) &&
2,586✔
1831
      rect->maxx - rect->minx > 360.0 &&
812✔
1832
      !reprojector->out->gt.need_geotransform) {
6✔
1833
    rect->maxx = 180;
6✔
1834
    rect->minx = -180;
6✔
1835
  }
1836

1837
  return MS_SUCCESS;
1838
}
1839

1840
/************************************************************************/
1841
/*                        msProjectHasLonWrap()                         */
1842
/************************************************************************/
1843

1844
int msProjectHasLonWrap(projectionObj *in, double *pdfLonWrap) {
4,900✔
1845
  int i;
1846
  if (pdfLonWrap)
4,900✔
1847
    *pdfLonWrap = 0;
4,900✔
1848

1849
  if (!msProjIsGeographicCRS(in))
4,900✔
1850
    return MS_FALSE;
1851

1852
  for (i = 0; i < in->numargs; i++) {
7,059✔
1853
    if (strncmp(in->args[i], "lon_wrap=", strlen("lon_wrap=")) == 0) {
4,156✔
1854
      if (pdfLonWrap)
12✔
1855
        *pdfLonWrap = atof(in->args[i] + strlen("lon_wrap="));
12✔
1856
      return MS_TRUE;
12✔
1857
    }
1858
  }
1859
  return MS_FALSE;
1860
}
1861

1862
/************************************************************************/
1863
/*                           msProjectRect()                            */
1864
/************************************************************************/
1865

1866
int msProjectRect(projectionObj *in, projectionObj *out, rectObj *rect) {
4,774✔
1867
  char *over = "+over";
4,774✔
1868
  int ret;
1869
  int bFreeInOver = MS_FALSE;
1870
  int bFreeOutOver = MS_FALSE;
1871
  projectionObj in_over, out_over, *inp, *outp;
1872
  double dfLonWrap = 0.0;
4,774✔
1873
  reprojectionObj *reprojector = NULL;
1874

1875
  /* Detect projecting from polar stereographic to longlat */
1876
  if (in && in->is_polar < 0 && !in->gt.need_geotransform && out &&
4,774✔
1877
      !out->gt.need_geotransform && !msProjIsGeographicCRS(in) &&
3,798✔
1878
      msProjIsGeographicCRS(out)) {
647✔
1879
    reprojector = msProjectCreateReprojector(in, out);
449✔
1880
    in->is_polar = 0;
449✔
1881
    for (int sign = 1; sign >= -1; sign -= 2) {
1,337✔
1882
      pointObj p;
1883
      p.x = 0.0;
893✔
1884
      p.y = 0.0;
893✔
1885
      if (reprojector && msProjectPointEx(reprojector, &p) == MS_SUCCESS &&
893✔
1886
          fabs(p.y - sign * 90) < 1e-8) {
893✔
1887
        in->is_polar = 1;
5✔
1888
        /* Is the pole in the rectangle ? */
1889
        if (0 >= rect->minx && 0 >= rect->miny && 0 <= rect->maxx &&
5✔
1890
            0 <= rect->maxy) {
5✔
1891
          if (msProjectRectAsPolygon(reprojector, rect) == MS_SUCCESS) {
4✔
1892
            rect->minx = -180.0;
4✔
1893
            rect->maxx = 180.0;
4✔
1894
            rect->maxy = sign * 90.0;
4✔
1895
            msProjectDestroyReprojector(reprojector);
4✔
1896
            return MS_SUCCESS;
5✔
1897
          }
1898
        }
1899
        /* Are we sure the dateline is not enclosed ? */
1900
        else if (rect->maxy < 0 || rect->maxx < 0 || rect->minx > 0) {
1✔
1901
          ret = msProjectRectAsPolygon(reprojector, rect);
1✔
1902
          msProjectDestroyReprojector(reprojector);
1✔
1903
          return ret;
1✔
1904
        }
1905
      }
1906
    }
1907
  }
1908

1909
  /* Detect projecting from polar stereographic to another projected system */
1910
  else if (in && in->is_polar < 0 && !in->gt.need_geotransform && out &&
4,325✔
1911
           !out->gt.need_geotransform && !msProjIsGeographicCRS(in) &&
2,900✔
1912
           !msProjIsGeographicCRS(out)) {
198✔
1913
    reprojectionObj *reprojectorToLongLat =
1914
        msProjectCreateReprojector(in, NULL);
198✔
1915
    in->is_polar = 0;
198✔
1916
    for (int sign = 1; sign >= -1; sign -= 2) {
592✔
1917
      pointObj p;
1918
      p.x = 0.0;
395✔
1919
      p.y = 0.0;
395✔
1920
      if (reprojectorToLongLat &&
790✔
1921
          msProjectPointEx(reprojectorToLongLat, &p) == MS_SUCCESS &&
395✔
1922
          fabs(p.y - 90) < 1e-8) {
395✔
1923
        in->is_polar = 1;
1✔
1924
        reprojector = msProjectCreateReprojector(in, out);
1✔
1925
        /* Is the pole in the rectangle ? */
1926
        if (0 >= rect->minx && 0 >= rect->miny && 0 <= rect->maxx &&
1✔
1927
            0 <= rect->maxy) {
1✔
1928
          if (msProjectRectAsPolygon(reprojector, rect) == MS_SUCCESS) {
1✔
1929
            msProjectDestroyReprojector(reprojector);
1✔
1930
            msProjectDestroyReprojector(reprojectorToLongLat);
1✔
1931
            return MS_SUCCESS;
1✔
1932
          }
1933
        }
1934
        /* Are we sure the dateline is not enclosed ? */
1935
        else if (rect->maxy < 0 || rect->maxx < 0 || rect->minx > 0) {
×
1936
          ret = msProjectRectAsPolygon(reprojector, rect);
×
1937
          msProjectDestroyReprojector(reprojector);
×
1938
          msProjectDestroyReprojector(reprojectorToLongLat);
×
1939
          return ret;
×
1940
        }
1941
      }
1942
    }
1943
    msProjectDestroyReprojector(reprojectorToLongLat);
197✔
1944
  }
1945

1946
  if (in && msProjectHasLonWrap(in, &dfLonWrap) && dfLonWrap == 180.0) {
4,768✔
1947
    inp = in;
1948
    outp = out;
1949
    if (rect->maxx > 180.0) {
8✔
1950
      rect->minx = -180.0;
8✔
1951
      rect->maxx = 180.0;
8✔
1952
    }
1953
  }
1954
  /*
1955
   * Issue #4892: When projecting a rectangle we do not want proj to wrap
1956
   * resulting coordinates around the dateline, as in practice a requested
1957
   * bounding box of -22.000.000, -YYY, 22.000.000, YYY should be projected as
1958
   * -190,-YYY,190,YYY rather than 170,-YYY,-170,YYY as would happen when
1959
   * wrapping (and vice-versa when projecting from lonlat to metric). To enforce
1960
   * this, we clone the input projections and add the "+over" proj parameter in
1961
   * order to disable dateline wrapping.
1962
   */
1963
  else {
1964
    int apply_over = MS_TRUE;
1965
#if PROJ_VERSION_MAJOR >= 6 && PROJ_VERSION_MAJOR < 9
1966
    // Workaround PROJ [6,9[ bug (fixed per
1967
    // https://github.com/OSGeo/PROJ/pull/3055) that prevents datum shifts from
1968
    // being applied when +over is added to +init=epsg:XXXX This is far from
1969
    // being bullet proof but it should work for most common use cases
1970
    if (in && in->proj) {
1971
      if (in->numargs == 1 && EQUAL(in->args[0], "init=epsg:4326") &&
1972
          rect->minx >= -180 && rect->maxx <= 180) {
1973
        apply_over = MS_FALSE;
1974
      } else if (in->numargs == 1 && EQUAL(in->args[0], "init=epsg:3857") &&
1975
                 rect->minx >= -20037508.3427892 &&
1976
                 rect->maxx <= 20037508.3427892) {
1977
        apply_over = MS_FALSE;
1978
      }
1979
    }
1980
#endif
1981
    // cppcheck-suppress knownConditionTrueFalse
1982
    if (out && apply_over && out->numargs > 0 &&
4,760✔
1983
        (strncmp(out->args[0], "init=", 5) == 0 ||
4,703✔
1984
         strncmp(out->args[0], "proj=", 5) == 0)) {
454✔
1985
      bFreeOutOver = MS_TRUE;
1986
      msInitProjection(&out_over);
4,701✔
1987
      msCopyProjectionExtended(&out_over, out, &over, 1);
4,701✔
1988
      outp = &out_over;
1989
      if (reprojector) {
4,701✔
1990
        msProjectDestroyReprojector(reprojector);
444✔
1991
        reprojector = NULL;
1992
      }
1993
    } else {
1994
      outp = out;
1995
    }
1996
    // cppcheck-suppress knownConditionTrueFalse
1997
    if (in && apply_over && in->numargs > 0 &&
4,760✔
1998
        (strncmp(in->args[0], "init=", 5) == 0 ||
4,756✔
1999
         strncmp(in->args[0], "proj=", 5) == 0)) {
315✔
2000
      bFreeInOver = MS_TRUE;
2001
      msInitProjection(&in_over);
4,751✔
2002
      msCopyProjectionExtended(&in_over, in, &over, 1);
4,751✔
2003
      inp = &in_over;
2004
      /* coverity[dead_error_begin] */
2005
      if (reprojector) {
4,751✔
2006
        msProjectDestroyReprojector(reprojector);
×
2007
        reprojector = NULL;
2008
      }
2009
    } else {
2010
      inp = in;
2011
    }
2012
  }
2013
  if (reprojector == NULL) {
4,768✔
2014
    reprojector = msProjectCreateReprojector(inp, outp);
4,768✔
2015
  }
2016
  ret = reprojector ? msProjectRectAsPolygon(reprojector, rect) : MS_FAILURE;
4,768✔
2017
  msProjectDestroyReprojector(reprojector);
4,768✔
2018
  if (bFreeInOver)
4,768✔
2019
    msFreeProjection(&in_over);
4,751✔
2020
  if (bFreeOutOver)
4,768✔
2021
    msFreeProjection(&out_over);
4,701✔
2022
  return ret;
2023
}
2024

2025
/************************************************************************/
2026
/*                        msProjectionsDiffer()                         */
2027
/************************************************************************/
2028

2029
/*
2030
** Compare two projections, and return MS_TRUE if they differ.
2031
**
2032
** For now this is implemented by exact comparison of the projection
2033
** arguments, but eventually this should call a PROJ.4 function with
2034
** more awareness of the issues.
2035
**
2036
** NOTE: MS_FALSE is returned if either of the projection objects
2037
** has no arguments, since reprojection won't work anyways.
2038
*/
2039

2040
static int msProjectionsDifferInternal(projectionObj *proj1,
8,376✔
2041
                                       projectionObj *proj2)
2042

2043
{
2044
  int i;
2045

2046
  if (proj1->numargs == 0 || proj2->numargs == 0)
8,376✔
2047
    return MS_FALSE;
2048

2049
  if (proj1->numargs != proj2->numargs)
6,998✔
2050
    return MS_TRUE;
2051

2052
  /* This test should be more rigerous. */
2053
  if (proj1->gt.need_geotransform || proj2->gt.need_geotransform)
3,794✔
2054
    return MS_TRUE;
2055

2056
  for (i = 0; i < proj1->numargs; i++) {
6,114✔
2057
    if (strcmp(proj1->args[i], proj2->args[i]) != 0)
3,815✔
2058
      return MS_TRUE;
2059
  }
2060

2061
  return MS_FALSE;
2062
}
2063

2064
int msProjectionsDiffer(projectionObj *proj1, projectionObj *proj2) {
8,376✔
2065
  return msProjectionsDifferInternal(proj1, proj2);
8,376✔
2066
}
2067

2068
/************************************************************************/
2069
/*                           msTestNeedWrap()                           */
2070
/************************************************************************/
2071
/*
2072

2073
Frank Warmerdam, Nov, 2001.
2074

2075
See Also:
2076

2077
http://mapserver.gis.umn.edu/bugs/show_bug.cgi?id=15
2078

2079
Proposal:
2080

2081
Modify msProjectLine() so that it "dateline wraps" objects when necessary
2082
in order to preserve their shape when reprojecting to lat/long.  This
2083
will be accomplished by:
2084

2085
1) As each vertex is reprojected, compare the X distance between that
2086
   vertex and the previous vertex.  If it is less than 180 then proceed to
2087
   the next vertex without any special logic, otherwise:
2088

2089
2) Reproject the center point of the line segment from the last vertex to
2090
   the current vertex into lat/long.  Does it's longitude lie between the
2091
   longitudes of the start and end point.  If yes, return to step 1) for
2092
   the next vertex ... everything is fine.
2093

2094
3) We have determined that this line segment is suffering from 360 degree
2095
   wrap to keep in the -180 to +180 range.  Now add or subtract 360 degrees
2096
   as determined from the original sign of the distances.
2097

2098
This is similar to the code there now (though disabled in CVS); however,
2099
it will ensure that big boxes will remain big, and not get dateline wrapped
2100
because of the extra test in step 2).  However step 2 is invoked only very
2101
rarely so this process takes little more than the normal process.  In fact,
2102
if we were sufficiently concerned about performance we could do a test on the
2103
shape MBR in lat/long space, and if the width is less than 180 we know we never
2104
need to even do test 1).
2105

2106
What doesn't this resolve:
2107

2108
This ensures that individual lines are kept in the proper shape when
2109
reprojected to geographic space.  However, it does not:
2110

2111
 o Ensure that all rings of a polygon will get transformed to the same "side"
2112
   of the world.  Depending on starting points of the different rings it is
2113
   entirely possible for one ring to end up in the -180 area and another ring
2114
   from the same polygon to end up in the +180 area.  We might possibly be
2115
   able to achieve this though, by treating the multi-ring polygon as a whole
2116
   and testing the first point of each additional ring against the last
2117
   vertex of the previous ring (or any previous vertex for that matter).
2118

2119
 o It does not address the need to cut up lines and polygons into distinct
2120
   chunks to preserve the correct semantics.  Really a polygon that
2121
   spaces the dateline in a -180 to 180 view should get split into two
2122
   polygons.  We haven't addressed that, though if it were to be addressed,
2123
   it could be done as a followon and distinct step from what we are doing
2124
   above.  In fact this sort of improvement (split polygons based on dateline
2125
   or view window) should be done for all lat/long shapes regardless of
2126
   whether they are being reprojected from another projection.
2127

2128
 o It does not address issues related to viewing rectangles that go outside
2129
   the -180 to 180 longitude range.  For instance, it is entirely reasonable
2130
   to want a 160 to 200 longitude view to see an area on the dateline clearly.
2131
   Currently shapes in the -180 to -160 range which should be displayed in the
2132
   180 to 200 portion of that view will not be because there is no recogition
2133
   that they belong there.
2134

2135

2136
*/
2137

2138
static int msTestNeedWrap(pointObj pt1, pointObj pt2, pointObj pt2_geo,
685✔
2139
                          reprojectionObj *reprojector)
2140

2141
{
2142
  pointObj middle;
2143
  projectionObj *out = reprojector->out;
685✔
2144

2145
  middle.x = (pt1.x + pt2.x) * 0.5;
685✔
2146
  middle.y = (pt1.y + pt2.y) * 0.5;
685✔
2147

2148
  if (msProjectPointEx(reprojector, &pt1) == MS_FAILURE ||
1,370✔
2149
      msProjectPointEx(reprojector, &pt2) == MS_FAILURE ||
1,362✔
2150
      msProjectPointEx(reprojector, &middle) == MS_FAILURE)
677✔
2151
    return 0;
8✔
2152

2153
  /*
2154
   * If the last point was moved, then we are considered due for a
2155
   * move to.
2156
   */
2157
  if (out->gt.need_geotransform && out->gt.geotransform[2] == 0) {
677✔
2158
    if (fabs((pt2_geo.x - pt2.x) * out->gt.geotransform[1]) > 180.0)
×
2159
      return 1;
2160
  } else {
2161
    if (fabs(pt2_geo.x - pt2.x) > 180.0)
677✔
2162
      return 1;
2163
  }
2164

2165
  /*
2166
   * Otherwise, test to see if the middle point transforms
2167
   * to be between the end points. If yes, no wrapping is needed.
2168
   * Otherwise wrapping is needed.
2169
   */
2170
  if ((middle.x < pt1.x && middle.x < pt2_geo.x) ||
17✔
2171
      (middle.x > pt1.x && middle.x > pt2_geo.x))
1✔
2172
    return 1;
2173
  else
2174
    return 0;
2175
}
2176

2177
/************************************************************************/
2178
/*                       msProjDataInitFromEnv()                        */
2179
/************************************************************************/
2180
void msProjDataInitFromEnv() {
3,211✔
2181
  const char *val;
2182

2183
  if ((val = CPLGetConfigOption("PROJ_DATA", NULL)) != NULL ||
6,422✔
2184
      (val = CPLGetConfigOption("PROJ_LIB", NULL)) != NULL) {
3,211✔
2185
    msSetPROJ_DATA(val, NULL);
×
2186
  }
2187
}
3,211✔
2188

2189
/************************************************************************/
2190
/*                           msSetPROJ_DATA()                           */
2191
/************************************************************************/
2192
void msSetPROJ_DATA(const char *proj_data, const char *pszRelToPath)
2,597✔
2193

2194
{
2195
  char *extended_path = NULL;
2196

2197
  /* Handle relative path if applicable */
2198
  if (proj_data && pszRelToPath && proj_data[0] != '/' &&
2,597✔
2199
      proj_data[0] != '\\' && !(proj_data[0] != '\0' && proj_data[1] == ':')) {
×
2200
    struct stat stat_buf;
2201
    extended_path =
2202
        (char *)msSmallMalloc(strlen(pszRelToPath) + strlen(proj_data) + 10);
×
2203
    sprintf(extended_path, "%s/%s", pszRelToPath, proj_data);
2204

2205
#ifndef S_ISDIR
2206
#define S_ISDIR(x) ((x)&S_IFDIR)
2207
#endif
2208

2209
    if (stat(extended_path, &stat_buf) == 0 && S_ISDIR(stat_buf.st_mode))
×
2210
      proj_data = extended_path;
2211
  }
2212

2213
  msAcquireLock(TLOCK_PROJ);
2,597✔
2214

2215
  if (proj_data == NULL && ms_proj_data == NULL) {
2,597✔
2216
    /* do nothing */
2217
  } else if (proj_data != NULL && ms_proj_data != NULL &&
×
2218
             strcmp(proj_data, ms_proj_data) == 0) {
×
2219
    /* do nothing */
2220
  } else {
2221
    ms_proj_data_change_counter++;
×
2222
    free(ms_proj_data);
×
2223
    ms_proj_data = proj_data ? msStrdup(proj_data) : NULL;
×
2224
  }
2225

2226
  msReleaseLock(TLOCK_PROJ);
2,597✔
2227

2228
  if (ms_proj_data != NULL) {
2,597✔
2229
#ifdef _WIN32
2230
    const char *sep = ";";
2231
#else
2232
    const char *sep = ":";
2233
#endif
2234
    char **papszPaths = CSLTokenizeString2(ms_proj_data, sep, 0);
×
2235
    OSRSetPROJSearchPaths((const char *const *)papszPaths);
×
2236
    CSLDestroy(papszPaths);
×
2237
  }
2238

2239
  if (extended_path)
2,597✔
2240
    msFree(extended_path);
×
2241
}
2,597✔
2242

2243
/************************************************************************/
2244
/*                       msGetProjectionString()                        */
2245
/*                                                                      */
2246
/*      Return the projection string.                                   */
2247
/************************************************************************/
2248

2249
char *msGetProjectionString(projectionObj *proj) {
147✔
2250
  char *pszProjString = NULL;
2251
  int nLen = 0;
2252

2253
  if (proj) {
147✔
2254
    /* -------------------------------------------------------------------- */
2255
    /*      Alloc buffer large enough to hold the whole projection defn     */
2256
    /* -------------------------------------------------------------------- */
2257
    for (int i = 0; i < proj->numargs; i++) {
311✔
2258
      if (proj->args[i])
164✔
2259
        nLen += (strlen(proj->args[i]) + 2);
164✔
2260
    }
2261

2262
    pszProjString = (char *)malloc(sizeof(char) * nLen + 1);
147✔
2263
    pszProjString[0] = '\0';
147✔
2264

2265
    /* -------------------------------------------------------------------- */
2266
    /*      Plug each arg into the string with a '+' prefix                 */
2267
    /* -------------------------------------------------------------------- */
2268
    for (int i = 0; i < proj->numargs; i++) {
311✔
2269
      if (!proj->args[i] || strlen(proj->args[i]) == 0)
164✔
2270
        continue;
×
2271
      if (pszProjString[0] == '\0') {
164✔
2272
        /* no space at beginning of line */
2273
        if (proj->args[i][0] != '+')
147✔
2274
          strcat(pszProjString, "+");
2275
      } else {
2276
        if (proj->args[i][0] != '+')
17✔
2277
          strcat(pszProjString, " +");
2278
        else
2279
          strcat(pszProjString, " ");
2280
      }
2281
      strcat(pszProjString, proj->args[i]);
164✔
2282
    }
2283
  }
2284

2285
  return pszProjString;
147✔
2286
}
2287

2288
/************************************************************************/
2289
/*                       msIsAxisInvertedProj()                         */
2290
/*                                                                      */
2291
/*      Return MS_TRUE is the proj object has epgsaxis=ne               */
2292
/************************************************************************/
2293

2294
int msIsAxisInvertedProj(projectionObj *proj) {
3,213✔
2295
  int i;
2296
  const char *axis = NULL;
2297

2298
  for (i = 0; i < proj->numargs; i++) {
6,426✔
2299
    if (strstr(proj->args[i], "epsgaxis=") != NULL) {
5,630✔
2300
      axis = strstr(proj->args[i], "=") + 1;
2,417✔
2301
      break;
2302
    }
2303
  }
2304

2305
  if (axis == NULL)
2306
    return MS_FALSE;
2307

2308
  if (strcasecmp(axis, "en") == 0)
2,417✔
2309
    return MS_FALSE;
2310

2311
  if (strcasecmp(axis, "ne") != 0) {
2,417✔
2312
    msDebug("msIsAxisInvertedProj(): odd +epsgaxis= value: '%s'.", axis);
×
2313
    return MS_FALSE;
×
2314
  }
2315

2316
  return MS_TRUE;
2317
}
2318

2319
/************************************************************************/
2320
/*                       msAxisNormalizePoints()                        */
2321
/*                                                                      */
2322
/*      Convert the passed points to "easting, northing" axis           */
2323
/*      orientation if they are not already.                            */
2324
/************************************************************************/
2325

2326
void msAxisNormalizePoints(projectionObj *proj, int count, double *x, double *y)
2,044✔
2327

2328
{
2329
  int i;
2330
  if (!msIsAxisInvertedProj(proj))
2,044✔
2331
    return;
2332

2333
  /* Switch axes */
2334
  for (i = 0; i < count; i++) {
3,792✔
2335
    double tmp;
2336

2337
    tmp = x[i];
1,896✔
2338
    x[i] = y[i];
1,896✔
2339
    y[i] = tmp;
1,896✔
2340
  }
2341
}
2342

2343
/************************************************************************/
2344
/*                             msAxisSwapShape                          */
2345
/*                                                                      */
2346
/*      Utility function to swap x and y coordiatesn Use for now for    */
2347
/*      WFS 1.1.x                                                       */
2348
/************************************************************************/
2349
void msAxisSwapShape(shapeObj *shape) {
1,186✔
2350
  double tmp;
2351
  int i, j;
2352

2353
  if (shape) {
1,186✔
2354
    for (i = 0; i < shape->numlines; i++) {
2,384✔
2355
      for (j = 0; j < shape->line[i].numpoints; j++) {
85,053✔
2356
        tmp = shape->line[i].point[j].x;
83,855✔
2357
        shape->line[i].point[j].x = shape->line[i].point[j].y;
83,855✔
2358
        shape->line[i].point[j].y = tmp;
83,855✔
2359
      }
2360
    }
2361

2362
    /*swap bounds*/
2363
    tmp = shape->bounds.minx;
1,186✔
2364
    shape->bounds.minx = shape->bounds.miny;
1,186✔
2365
    shape->bounds.miny = tmp;
1,186✔
2366

2367
    tmp = shape->bounds.maxx;
1,186✔
2368
    shape->bounds.maxx = shape->bounds.maxy;
1,186✔
2369
    shape->bounds.maxy = tmp;
1,186✔
2370
  }
2371
}
1,186✔
2372

2373
/************************************************************************/
2374
/*                      msAxisDenormalizePoints()                       */
2375
/*                                                                      */
2376
/*      Convert points from easting,northing orientation to the         */
2377
/*      preferred epsg orientation of this projectionObj.               */
2378
/************************************************************************/
2379

2380
void msAxisDenormalizePoints(projectionObj *proj, int count, double *x,
×
2381
                             double *y)
2382

2383
{
2384
  /* For how this is essentially identical to normalizing */
2385
  msAxisNormalizePoints(proj, count, x, y);
×
2386
}
×
2387

2388
/************************************************************************/
2389
/*                        msProjIsGeographicCRS()                       */
2390
/*                                                                      */
2391
/*      Returns whether a CRS is a geographic one.                      */
2392
/************************************************************************/
2393

2394
int msProjIsGeographicCRS(projectionObj *proj) {
79,303✔
2395
  PJ_TYPE type;
2396
  if (!proj->proj)
79,303✔
2397
    return FALSE;
2398
  type = proj_get_type(proj->proj);
79,211✔
2399
  if (type == PJ_TYPE_GEOGRAPHIC_2D_CRS || type == PJ_TYPE_GEOGRAPHIC_3D_CRS)
79,211✔
2400
    return TRUE;
2401
  if (type == PJ_TYPE_BOUND_CRS) {
37,499✔
2402
    PJ *base_crs = proj_get_source_crs(proj->proj_ctx->proj_ctx, proj->proj);
192✔
2403
    type = proj_get_type(base_crs);
192✔
2404
    proj_destroy(base_crs);
192✔
2405
    return type == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
192✔
2406
           type == PJ_TYPE_GEOGRAPHIC_3D_CRS;
2407
  }
2408
  return FALSE;
2409
}
2410

2411
/************************************************************************/
2412
/*                        ConvertProjUnitStringToMS                     */
2413
/*                                                                      */
2414
/*       Returns mapserver's unit code corresponding to the proj        */
2415
/*      unit passed as argument.                                        */
2416
/*       Please refer to ./src/pj_units.c file in the Proj.4 module.    */
2417
/************************************************************************/
2418
static int ConvertProjUnitStringToMS(const char *pszProjUnit) {
194✔
2419
  if (strcmp(pszProjUnit, "m") == 0) {
194✔
2420
    return MS_METERS;
2421
  } else if (strcmp(pszProjUnit, "km") == 0) {
×
2422
    return MS_KILOMETERS;
2423
  } else if (strcmp(pszProjUnit, "mi") == 0 ||
×
2424
             strcmp(pszProjUnit, "us-mi") == 0) {
×
2425
    return MS_MILES;
2426
  } else if (strcmp(pszProjUnit, "in") == 0 ||
×
2427
             strcmp(pszProjUnit, "us-in") == 0) {
×
2428
    return MS_INCHES;
2429
  } else if (strcmp(pszProjUnit, "ft") == 0 ||
×
2430
             strcmp(pszProjUnit, "us-ft") == 0) {
×
2431
    return MS_FEET;
2432
  } else if (strcmp(pszProjUnit, "kmi") == 0) {
×
2433
    return MS_NAUTICALMILES;
×
2434
  }
2435

2436
  return -1;
2437
}
2438

2439
/************************************************************************/
2440
/*           int GetMapserverUnitUsingProj(projectionObj *psProj)       */
2441
/*                                                                      */
2442
/*      Return a mapserver unit corresponding to the projection         */
2443
/*      passed. Return -1 on failure                                    */
2444
/************************************************************************/
2445
int GetMapserverUnitUsingProj(projectionObj *psProj) {
1,116✔
2446
  if (msProjIsGeographicCRS(psProj))
1,116✔
2447
    return MS_DD;
2448

2449
  const char *proj_str = proj_as_proj_string(psProj->proj_ctx->proj_ctx,
194✔
2450
                                             psProj->proj, PJ_PROJ_4, NULL);
194✔
2451
  if (proj_str == NULL)
194✔
2452
    return -1;
2453

2454
  /* -------------------------------------------------------------------- */
2455
  /*      Handle case of named units.                                     */
2456
  /* -------------------------------------------------------------------- */
2457
  if (strstr(proj_str, "units=") != NULL) {
194✔
2458
    char units[32];
2459
    char *blank;
2460

2461
    strlcpy(units, (strstr(proj_str, "units=") + 6), sizeof(units));
194✔
2462

2463
    blank = strchr(units, ' ');
194✔
2464
    if (blank != NULL)
194✔
2465
      *blank = '\0';
194✔
2466

2467
    return ConvertProjUnitStringToMS(units);
194✔
2468
  }
2469

2470
  /* -------------------------------------------------------------------- */
2471
  /*      Handle case of to_meter value.                                  */
2472
  /* -------------------------------------------------------------------- */
2473
  if (strstr(proj_str, "to_meter=") != NULL) {
×
2474
    char to_meter_str[32];
2475
    char *blank;
2476
    double to_meter;
2477

2478
    strlcpy(to_meter_str, (strstr(proj_str, "to_meter=") + 9),
×
2479
            sizeof(to_meter_str));
2480

2481
    blank = strchr(to_meter_str, ' ');
×
2482
    if (blank != NULL)
×
2483
      *blank = '\0';
×
2484

2485
    to_meter = atof(to_meter_str);
2486

2487
    if (fabs(to_meter - 1.0) < 0.0000001)
×
2488
      return MS_METERS;
2489
    else if (fabs(to_meter - 1000.0) < 0.00001)
×
2490
      return MS_KILOMETERS;
2491
    else if (fabs(to_meter - 0.3048) < 0.0001)
×
2492
      return MS_FEET;
2493
    else if (fabs(to_meter - 0.0254) < 0.0001)
×
2494
      return MS_INCHES;
2495
    else if (fabs(to_meter - 1609.344) < 0.001)
×
2496
      return MS_MILES;
2497
    else if (fabs(to_meter - 1852.0) < 0.1)
×
2498
      return MS_NAUTICALMILES;
2499
    else
2500
      return -1;
×
2501
  }
2502

2503
  return -1;
2504
}
2505

2506
/************************************************************************/
2507
/*                   msProjectionContextGetFromPool()                   */
2508
/*                                                                      */
2509
/*       Returns a projection context from the pool, or create a new    */
2510
/*       one if the pool is empty.                                      */
2511
/*       After use, it should normally be returned with                 */
2512
/*       msProjectionContextReleaseToPool()                             */
2513
/************************************************************************/
2514

2515
projectionContext *msProjectionContextGetFromPool() {
3,168✔
2516
  projectionContext *context;
2517
  msAcquireLock(TLOCK_PROJ);
3,168✔
2518

2519
  if (headOfLinkedListOfProjContext) {
3,168✔
2520
    LinkedListOfProjContext *next = headOfLinkedListOfProjContext->next;
524✔
2521
    context = headOfLinkedListOfProjContext->context;
524✔
2522
    context->thread_id = msGetThreadId();
524✔
2523
    msFree(headOfLinkedListOfProjContext);
524✔
2524
    headOfLinkedListOfProjContext = next;
524✔
2525
  } else {
2526
    context = msProjectionContextCreate();
2,644✔
2527
  }
2528

2529
  msReleaseLock(TLOCK_PROJ);
3,168✔
2530
  return context;
3,168✔
2531
}
2532

2533
/************************************************************************/
2534
/*                  msProjectionContextReleaseToPool()                  */
2535
/************************************************************************/
2536

2537
void msProjectionContextReleaseToPool(projectionContext *ctx) {
3,159✔
2538
  LinkedListOfProjContext *link =
2539
      (LinkedListOfProjContext *)msSmallMalloc(sizeof(LinkedListOfProjContext));
3,159✔
2540
  link->context = ctx;
3,159✔
2541
  msAcquireLock(TLOCK_PROJ);
3,159✔
2542
  link->next = headOfLinkedListOfProjContext;
3,159✔
2543
  headOfLinkedListOfProjContext = link;
3,159✔
2544
  msReleaseLock(TLOCK_PROJ);
3,159✔
2545
}
3,159✔
2546

2547
/************************************************************************/
2548
/*                   msProjectionContextPoolCleanup()                   */
2549
/************************************************************************/
2550

2551
void msProjectionContextPoolCleanup() {
2,597✔
2552
  LinkedListOfProjContext *link;
2553
  msAcquireLock(TLOCK_PROJ);
2,597✔
2554
  link = headOfLinkedListOfProjContext;
2,597✔
2555
  while (link) {
5,226✔
2556
    LinkedListOfProjContext *next = link->next;
2,629✔
2557
    msProjectionContextUnref(link->context);
2,629✔
2558
    msFree(link);
2,629✔
2559
    link = next;
2560
  }
2561
  headOfLinkedListOfProjContext = NULL;
2,597✔
2562
  msReleaseLock(TLOCK_PROJ);
2,597✔
2563
}
2,597✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc