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

MapServer / MapServer / 25968262348

16 May 2026 05:24PM UTC coverage: 42.534% (+0.1%) from 42.439%
25968262348

Pull #7507

github

web-flow
Merge 4053aab2f into 9e1ae01f6
Pull Request #7507: Implementation for MS RFC 142 (scalebar geodesic measurement)

178 of 199 new or added lines in 4 files covered. (89.45%)

25425 existing lines in 4 files now uncovered.

64905 of 152594 relevant lines covered (42.53%)

27351.34 hits per line

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

70.16
/src/mapscale.c
1
/******************************************************************************
2
 * $Id$
3
 *
4
 * Project:  MapServer
5
 * Purpose:  Scale object rendering.
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

32
#define VMARGIN 3 /* buffer around the scalebar */
33
#define HMARGIN 3
34
#define VSPACING .8 /* spacing (% of font height) between scalebar and text */
35
#define VSLOP 5     /* makes things fit a bit better vertically */
36

37
/*
38
** Match this with with unit enumerations is mapserver.h
39
*/
40
static char *unitText[9] = {
41
    "in", "ft", "mi", "m", "km",
42
    "dd", "??", "??", "NM"}; /* MS_PIXEL and MS_PERCENTAGE not used */
43
double inchesPerUnit[9] = {1,       12, 63360.0, 39.3701,   39370.1,
44
                           4374754, 1,  1,       72913.3858};
45

46
static double roundInterval(double d) {
1,072✔
47
  if (d < .001)
1,072✔
48
    return (MS_NINT(d * 10000) / 10000.0);
×
49
  if (d < .01)
1,072✔
50
    return (MS_NINT(d * 1000) / 1000.0);
×
51
  if (d < .1)
1,072✔
52
    return (MS_NINT(d * 100) / 100.0);
×
53
  if (d < 1)
1,072✔
54
    return (MS_NINT(d * 10) / 10.0);
1,034✔
55
  if (d < 100)
38✔
56
    return (MS_NINT(d));
14✔
57
  if (d < 1000)
24✔
58
    return (MS_NINT(d / 10) * 10);
18✔
59
  if (d < 10000)
6✔
60
    return (MS_NINT(d / 100) * 100);
×
61
  if (d < 100000)
6✔
62
    return (MS_NINT(d / 1000) * 1000);
6✔
63
  if (d < 1000000)
×
64
    return (MS_NINT(d / 10000) * 10000);
×
65
  if (d < 10000000)
×
66
    return (MS_NINT(d / 100000) * 100000);
×
67
  if (d < 100000000)
×
68
    return (MS_NINT(d / 1000000) * 1000000);
×
69

70
  return (-1);
71
}
72

73
/*
74
** Calculate the approximate scale based on a few parameters. Note that this
75
*assumes the scale is
76
** the same in the x direction as in the y direction, so run msAdjustExtent(...)
77
*first.
78
*/
79
int msCalculateScale(rectObj extent, int units, int width, int height,
1,681✔
80
                     double resolution, double *scale) {
81
  double md, gd, center_y;
82

83
  /* if((extent.maxx == extent.minx) || (extent.maxy == extent.miny))   */
84
  if (!MS_VALID_EXTENT(extent)) {
1,681✔
85
    msSetError(MS_MISCERR,
×
86
               "Invalid image extent, minx=%lf, miny=%lf, maxx=%lf, maxy=%lf.",
87
               "msCalculateScale()", extent.minx, extent.miny, extent.maxx,
88
               extent.maxy);
89
    return (MS_FAILURE);
×
90
  }
91

92
  if ((width <= 0) || (height <= 0)) {
1,681✔
93
    msSetError(MS_MISCERR, "Invalid image width or height.",
8✔
94
               "msCalculateScale()");
95
    return (MS_FAILURE);
8✔
96
  }
97

98
  switch (units) {
1,673✔
99
  case (MS_DD):
1,673✔
100
  case (MS_METERS):
101
  case (MS_KILOMETERS):
102
  case (MS_MILES):
103
  case (MS_NAUTICALMILES):
104
  case (MS_INCHES):
105
  case (MS_FEET):
106
    center_y = (extent.miny + extent.maxy) / 2.0;
1,673✔
107
    md = (width - 1) /
3,346✔
108
         (resolution *
1,673✔
109
          msInchesPerUnit(
1,673✔
110
              units, center_y)); /* remember, we use a pixel-center to
111
                                    pixel-center extent, hence the width-1 */
112
    gd = extent.maxx - extent.minx;
1,673✔
113
    *scale = gd / md;
1,673✔
114
    break;
1,673✔
115
  default:
×
116
    *scale = -1; /* this is not an error */
×
117
    break;
×
118
  }
119

120
  return (MS_SUCCESS);
121
}
122

123
double msInchesPerUnit(int units, double center_lat) {
4,152✔
124
  (void)center_lat;
125
  double lat_adj = 1.0, ipu = 1.0;
126

127
  switch (units) {
4,152✔
128
  case (MS_METERS):
3,637✔
129
  case (MS_KILOMETERS):
130
  case (MS_MILES):
131
  case (MS_NAUTICALMILES):
132
  case (MS_INCHES):
133
  case (MS_FEET):
134
    ipu = inchesPerUnit[units];
3,637✔
135
    break;
3,637✔
136
  case (MS_DD):
515✔
137
    /* With geographical (DD) coordinates, we adjust the inchesPerUnit
138
     * based on the latitude of the center of the view. For this we assume
139
     * we have a perfect sphere and just use cos(lat) in our calculation.
140
     */
141
#ifdef ENABLE_VARIABLE_INCHES_PER_DEGREE
142
    if (center_lat != 0.0) {
143
      double cos_lat;
144
      cos_lat = cos(MS_PI * center_lat / 180.0);
145
      lat_adj = sqrt(1 + cos_lat * cos_lat) / sqrt(2.0);
146
    }
147
#endif
148
    ipu = inchesPerUnit[units] * lat_adj;
515✔
149
    break;
515✔
150
  default:
151
    break;
152
  }
153

154
  return ipu;
4,152✔
155
}
156

157
#define X_STEP_SIZE 5
158

159
static double msScalebarMeasurePixelSpanCartesian(mapObj *map,
1,067✔
160
                                                  const scalebarObj *scalebar,
161
                                                  double pixel_width) {
162
  return MS_CONVERT_UNIT(map->units, scalebar->units,
1,067✔
163
                         map->cellsize * pixel_width);
164
}
165

166
static void msScalebarSamplePixel(const mapObj *map, double *px, double *py) {
22✔
167
  double y;
168

169
  switch (map->scalebar.position) {
22✔
170
  case MS_LL:
12✔
171
  case MS_LR:
172
  case MS_LC:
173
    y = map->height - map->scalebar.offsety - 1.0;
12✔
174
    break;
12✔
175
  case MS_UL:
4✔
176
  case MS_UR:
177
  case MS_UC:
178
    y = map->scalebar.offsety;
4✔
179
    break;
4✔
180
  default:
6✔
181
    y = map->height * 0.5;
6✔
182
    break;
6✔
183
  }
184

185
  *px = map->width * 0.5;
22✔
186
  *py = MS_MAX(0.0, MS_MIN(y, map->height - 1.0));
22✔
187
}
22✔
188

189
static int msScalebarProjectPointToLatLon(mapObj *map, pointObj *point) {
43✔
190
  if (map->projection.proj) {
43✔
191
    if (msProjectPoint(&map->projection, &map->latlon, point) == MS_SUCCESS)
38✔
192
      return MS_SUCCESS;
193
  }
194

195
  if (!map->projection.proj && map->units == MS_DD)
5✔
196
    return MS_SUCCESS;
197

198
  if (map->projection.proj)
1✔
NEW
199
    msSetError(MS_PROJERR,
×
200
               "Failed to project scalebar measurement endpoint to "
201
               "geographic coordinates.",
202
               "msDrawScalebar()");
203
  else
204
    msSetError(MS_MISCERR,
1✔
205
               "Geodesic scalebar measurement requires a map projection or "
206
               "decimal degree map units.",
207
               "msDrawScalebar()");
208
  return MS_FAILURE;
209
}
210

211
static int msScalebarMeasurePixelSpanGeodesic(mapObj *map,
22✔
212
                                              const scalebarObj *scalebar,
213
                                              double pixel_width,
214
                                              double *distance) {
215
  pointObj p1, p2;
216
  PJ_COORD c1, c2, geod;
217
  double sample_px, sample_py;
218
  double sample_x, sample_y;
219
  const double half_width = map->cellsize * pixel_width / 2.0;
22✔
220

221
  /*
222
   * GEODESIC scalebars are local measurements. POSITION and OFFSET select a
223
   * representative vertical sample row, while the horizontal sample remains
224
   * centered in the map.
225
   */
226
  if (!map->latlon.proj) {
22✔
NEW
227
    msSetError(MS_MISCERR,
×
228
               "Geodesic scalebar measurement requires a geographic "
229
               "projection definition.",
230
               "msDrawScalebar()");
NEW
231
    return MS_FAILURE;
×
232
  }
233

234
  msScalebarSamplePixel(map, &sample_px, &sample_py);
22✔
235
  sample_x = map->extent.minx + sample_px * map->cellsize;
22✔
236
  sample_y = map->extent.maxy - sample_py * map->cellsize;
22✔
237

238
  p1.x = sample_x - half_width;
22✔
239
  p1.y = sample_y;
22✔
240
  p2.x = sample_x + half_width;
22✔
241
  p2.y = sample_y;
22✔
242

243
  if (msScalebarProjectPointToLatLon(map, &p1) != MS_SUCCESS ||
43✔
244
      msScalebarProjectPointToLatLon(map, &p2) != MS_SUCCESS)
21✔
245
    return MS_FAILURE;
1✔
246

247
  c1.lp.lam = p1.x * MS_DEG_TO_RAD;
21✔
248
  c1.lp.phi = p1.y * MS_DEG_TO_RAD;
21✔
249
  c2.lp.lam = p2.x * MS_DEG_TO_RAD;
21✔
250
  c2.lp.phi = p2.y * MS_DEG_TO_RAD;
21✔
251

252
  geod = proj_geod(map->latlon.proj, c1, c2);
21✔
253
  if (!isfinite(geod.geod.s) || geod.geod.s <= 0) {
21✔
NEW
254
    msSetError(MS_PROJERR,
×
255
               "Failed to calculate a positive geodesic scalebar distance.",
256
               "msDrawScalebar()");
NEW
257
    return MS_FAILURE;
×
258
  }
259

260
  *distance = MS_CONVERT_UNIT(MS_METERS, scalebar->units, geod.geod.s);
21✔
261
  return MS_SUCCESS;
21✔
262
}
263

264
int msScalebarMeasurePixelSpan(mapObj *map, const scalebarObj *scalebar,
1,089✔
265
                               double pixel_width, double *distance) {
266
  int status;
267

268
  switch (scalebar->measure) {
1,089✔
269
  case MS_SCALEBAR_MEASURE_CARTESIAN:
1,067✔
270
    *distance = msScalebarMeasurePixelSpanCartesian(map, scalebar, pixel_width);
1,067✔
271
    status = MS_SUCCESS;
272
    break;
273
  case MS_SCALEBAR_MEASURE_GEODESIC:
22✔
274
    status = msScalebarMeasurePixelSpanGeodesic(map, scalebar, pixel_width,
22✔
275
                                                distance);
276
    break;
NEW
277
  default:
×
NEW
278
    msSetError(MS_MISCERR, "Unsupported scalebar measurement mode.",
×
279
               "msDrawScalebar()");
NEW
280
    return MS_FAILURE;
×
281
  }
282

283
  if (status != MS_SUCCESS)
22✔
284
    return MS_FAILURE;
285

286
  if (!isfinite(*distance) || *distance <= 0) {
1,088✔
NEW
287
    msSetError(MS_MISCERR,
×
288
               "Scalebar measurement did not produce a positive distance.",
289
               "msDrawScalebar()");
NEW
290
    return MS_FAILURE;
×
291
  }
292
  return MS_SUCCESS;
293
}
294

295
imageObj *msDrawScalebar(mapObj *map) {
170✔
296
  int status;
297
  char label[32];
298
  double i, msx;
299
  int j;
300
  int isx, sx, sy, ox, oy, state, dsx;
301
  pointObj p;
302
  rectObj r;
303
  imageObj *image = NULL;
304
  double fontWidth, fontHeight;
305
  outputFormatObj *format = NULL;
170✔
306
  strokeStyleObj strokeStyle = {0};
170✔
307
  shapeObj shape;
308
  lineObj line;
309
  pointObj points[5];
310
  textSymbolObj ts;
311
  rendererVTableObj *renderer;
312

313
  strokeStyle.patternlength = 0;
314
  initTextSymbol(&ts);
170✔
315

316
  if ((int)map->units == -1) {
170✔
317
    msSetError(MS_MISCERR, "Map units not set.", "msDrawScalebar()");
×
318
    return (NULL);
×
319
  }
320

321
  renderer = MS_MAP_RENDERER(map);
170✔
322
  if (!renderer || !MS_MAP_RENDERER(map)->supports_pixel_buffer) {
170✔
323
    msSetError(MS_MISCERR, "Outputformat not supported for scalebar",
×
324
               "msDrawScalebar()");
325
    return (NULL);
×
326
  }
327

328
  msPopulateTextSymbolForLabelAndString(
170✔
329
      &ts, &map->scalebar.label, msStrdup("0123456789"), 1.0,
330
      map->resolution / map->defresolution, 0);
170✔
331

332
  /*
333
   *  A string containing the ten decimal digits is rendered to compute an
334
   * average cell size for each number, which is used later to place labels on
335
   * the scalebar.
336
   */
337

338
  if (msGetTextSymbolSize(map, &ts, &r) != MS_SUCCESS) {
170✔
339
    return NULL;
340
  }
341
  fontWidth = (r.maxx - r.minx) / 10.0;
170✔
342
  fontHeight = r.maxy - r.miny;
170✔
343

344
  map->cellsize = msAdjustExtent(&(map->extent), map->width, map->height);
170✔
345
  status = msCalculateScale(map->extent, map->units, map->width, map->height,
170✔
346
                            map->resolution, &map->scaledenom);
347
  if (status != MS_SUCCESS) {
170✔
348
    return (NULL);
349
  }
350
  dsx = map->scalebar.width - 2 * HMARGIN;
170✔
351
  do {
903✔
352
    double units_per_pixel;
353
    if (msScalebarMeasurePixelSpan(map, &map->scalebar, dsx, &msx) !=
1,073✔
354
        MS_SUCCESS) {
355
      status = MS_FAILURE;
356
      goto scale_cleanup;
1✔
357
    }
358
    i = roundInterval(msx / map->scalebar.intervals);
1,072✔
359
    snprintf(label, sizeof(label), "%g",
1,072✔
360
             map->scalebar.intervals * i); /* last label */
361
    units_per_pixel = msx / dsx;
1,072✔
362
    isx = MS_NINT(i / units_per_pixel);
1,072✔
363
    sx = (map->scalebar.intervals * isx) +
1,072✔
364
         MS_NINT((1.5 + strlen(label) / 2.0 +
1,072✔
365
                  strlen(unitText[map->scalebar.units])) *
366
                 fontWidth);
367

368
    if (sx <= (map->scalebar.width - 2 * HMARGIN))
1,072✔
369
      break; /* it will fit */
370

371
    dsx -= X_STEP_SIZE; /* change the desired size in hopes that it will fit in
903✔
372
                           user supplied width */
373
  } while (1);
374

375
  sy = (2 * VMARGIN) + MS_NINT(VSPACING * fontHeight) + fontHeight +
169✔
376
       map->scalebar.height - VSLOP;
169✔
377

378
  /*Ensure we have an image format representing the options for the scalebar.*/
379
  msApplyOutputFormat(&format, map->outputformat, map->scalebar.transparent);
169✔
380

381
  if (map->scalebar.transparent == MS_OFF) {
169✔
382
    if (!MS_VALID_COLOR(map->scalebar.imagecolor))
8✔
383
      MS_INIT_COLOR(map->scalebar.imagecolor, 255, 255, 255, 255);
8✔
384
  }
385
  image = msImageCreate(map->scalebar.width, sy, format, map->web.imagepath,
169✔
386
                        map->web.imageurl, map->resolution, map->defresolution,
387
                        &map->scalebar.imagecolor);
388

389
  /* did we succeed in creating the image? */
390
  if (!image) {
169✔
391
    msSetError(MS_MISCERR, "Unable to initialize image.", "msDrawScalebar()");
×
392
    return NULL;
×
393
  }
394
  image->map = map;
169✔
395

396
  /* drop this reference to output format */
397
  msApplyOutputFormat(&format, NULL, MS_NOOVERRIDE);
169✔
398

399
  switch (map->scalebar.align) {
169✔
400
  case (MS_ALIGN_LEFT):
401
    ox = HMARGIN;
402
    break;
403
  case (MS_ALIGN_RIGHT):
×
404
    ox = MS_NINT((map->scalebar.width - sx) + fontWidth);
×
405
    break;
×
406
  default:
169✔
407
    ox = MS_NINT((map->scalebar.width - sx) / 2.0 +
169✔
408
                 fontWidth / 2.0); /* center the computed scalebar */
409
  }
410
  oy = VMARGIN;
411

412
  switch (map->scalebar.style) {
169✔
413
  case (0): {
169✔
414

415
    line.numpoints = 5;
169✔
416
    line.point = points;
169✔
417
    shape.line = &line;
169✔
418
    shape.numlines = 1;
169✔
419
    if (MS_VALID_COLOR(map->scalebar.color)) {
169✔
420
      INIT_STROKE_STYLE(strokeStyle);
169✔
421
      strokeStyle.color = &map->scalebar.outlinecolor;
169✔
422
      strokeStyle.color->alpha = 255;
169✔
423
      strokeStyle.width = 1;
169✔
424
    }
425
    map->scalebar.backgroundcolor.alpha = 255;
169✔
426
    map->scalebar.color.alpha = 255;
169✔
427
    state = 1; /* 1 means filled */
428
    for (j = 0; j < map->scalebar.intervals; j++) {
835✔
429
      points[0].x = points[4].x = points[3].x = ox + j * isx + 0.5;
666✔
430
      points[0].y = points[4].y = points[1].y = oy + 0.5;
666✔
431
      points[1].x = points[2].x = ox + (j + 1) * isx + 0.5;
666✔
432
      points[2].y = points[3].y = oy + map->scalebar.height + 0.5;
666✔
433
      if (state == 1 && MS_VALID_COLOR(map->scalebar.color))
666✔
434
        status = renderer->renderPolygon(image, &shape, &map->scalebar.color);
338✔
435
      else if (MS_VALID_COLOR(map->scalebar.backgroundcolor))
328✔
436
        status = renderer->renderPolygon(image, &shape,
324✔
437
                                         &map->scalebar.backgroundcolor);
438

439
      if (MS_UNLIKELY(status == MS_FAILURE)) {
666✔
440
        goto scale_cleanup;
×
441
      }
442

443
      if (strokeStyle.color) {
666✔
444
        status = renderer->renderLine(image, &shape, &strokeStyle);
666✔
445

446
        if (MS_UNLIKELY(status == MS_FAILURE)) {
666✔
447
          goto scale_cleanup;
×
448
        }
449
      }
450

451
      snprintf(label, sizeof(label), "%g", j * i);
666✔
452
      map->scalebar.label.position = MS_CC;
666✔
453
      p.x = ox + j * isx; /* + MS_NINT(fontPtr->w/2); */
666✔
454
      p.y = oy + map->scalebar.height + MS_NINT(VSPACING * fontHeight);
666✔
455
      status = msDrawLabel(map, image, p, msStrdup(label), &map->scalebar.label,
666✔
456
                           1.0);
457
      if (MS_UNLIKELY(status == MS_FAILURE)) {
666✔
458
        goto scale_cleanup;
×
459
      }
460
      state = -state;
666✔
461
    }
462
    snprintf(label, sizeof(label), "%g", j * i);
169✔
463
    ox = ox + j * isx - MS_NINT((strlen(label) * fontWidth) / 2.0);
169✔
464
    snprintf(label, sizeof(label), "%g %s", j * i,
169✔
465
             unitText[map->scalebar.units]);
169✔
466
    map->scalebar.label.position = MS_CR;
169✔
467
    p.x = ox; /* + MS_NINT(fontPtr->w/2); */
169✔
468
    p.y = oy + map->scalebar.height + MS_NINT(VSPACING * fontHeight);
169✔
469
    status =
470
        msDrawLabel(map, image, p, msStrdup(label), &map->scalebar.label, 1.0);
169✔
471
    if (MS_UNLIKELY(status == MS_FAILURE)) {
169✔
472
      goto scale_cleanup;
×
473
    }
474
    break;
475
  }
476
  case (1): {
×
477
    line.numpoints = 2;
×
478
    line.point = points;
×
479
    shape.line = &line;
×
480
    shape.numlines = 1;
×
481
    if (MS_VALID_COLOR(map->scalebar.color)) {
×
482
      strokeStyle.width = 1;
×
483
      strokeStyle.color = &map->scalebar.color;
×
484
    }
485

486
    points[0].y = points[1].y = oy;
×
487
    points[0].x = ox;
×
488
    points[1].x = ox + isx * map->scalebar.intervals;
×
489
    status = renderer->renderLine(image, &shape, &strokeStyle);
×
490
    if (MS_UNLIKELY(status == MS_FAILURE)) {
×
491
      goto scale_cleanup;
×
492
    }
493

494
    points[0].y = oy;
×
495
    points[1].y = oy + map->scalebar.height;
×
496
    p.y = oy + map->scalebar.height + MS_NINT(VSPACING * fontHeight);
×
497
    for (j = 0; j <= map->scalebar.intervals; j++) {
×
498
      points[0].x = points[1].x = ox + j * isx;
×
499
      status = renderer->renderLine(image, &shape, &strokeStyle);
×
500
      if (MS_UNLIKELY(status == MS_FAILURE)) {
×
501
        goto scale_cleanup;
×
502
      }
503

504
      snprintf(label, sizeof(label), "%g", j * i);
×
505
      if (j != map->scalebar.intervals) {
×
506
        map->scalebar.label.position = MS_CC;
×
507
        p.x = ox + j * isx; /* + MS_NINT(fontPtr->w/2); */
×
508
      } else {
509
        snprintf(label, sizeof(label), "%g %s", j * i,
×
510
                 unitText[map->scalebar.units]);
×
511
        map->scalebar.label.position = MS_CR;
×
512
        p.x = ox + j * isx - MS_NINT((strlen(label) * fontWidth) / 2.0);
×
513
      }
514
      status = msDrawLabel(map, image, p, msStrdup(label), &map->scalebar.label,
×
515
                           1.0);
516
      if (MS_UNLIKELY(status == MS_FAILURE)) {
×
517
        goto scale_cleanup;
×
518
      }
519
    }
520
    break;
521
  }
522
  default:
×
523
    msSetError(MS_MISCERR, "Unsupported scalebar style.", "msDrawScalebar()");
×
524
    status = MS_FAILURE;
NEW
525
    goto scale_cleanup;
×
526
  }
527

528
scale_cleanup:
170✔
529
  freeTextSymbol(&ts);
170✔
530
  if (MS_UNLIKELY(status == MS_FAILURE)) {
170✔
531
    msFreeImage(image);
1✔
532
    return NULL;
1✔
533
  }
534
  return (image);
535
}
536

537
int msEmbedScalebar(mapObj *map, imageObj *img) {
170✔
538
  int l, index, s, status = MS_SUCCESS;
539
  pointObj point;
540
  imageObj *image = NULL;
541
  rendererVTableObj *renderer;
542
  symbolObj *embeddedSymbol;
543
  char *imageType = NULL;
544

545
  index = msGetSymbolIndex(&(map->symbolset), "scalebar", MS_FALSE);
170✔
546
  if (index != -1)
170✔
547
    msRemoveSymbol(&(map->symbolset),
1✔
548
                   index); /* remove cached symbol in case the function is
549
   called multiple times with different zoom levels */
550

551
  if ((embeddedSymbol = msGrowSymbolSet(&map->symbolset)) == NULL)
170✔
552
    return MS_FAILURE;
553

554
  s = map->symbolset.numsymbols;
170✔
555
  map->symbolset.numsymbols++;
170✔
556

557
  if (!MS_RENDERER_PLUGIN(map->outputformat) ||
170✔
558
      !MS_MAP_RENDERER(map)->supports_pixel_buffer) {
170✔
559
    imageType = msStrdup(map->imagetype); /* save format */
×
560
    if MS_DRIVER_CAIRO (map->outputformat)
×
561
      map->outputformat = msSelectOutputFormat(map, "cairopng");
×
562
    else
563
      map->outputformat = msSelectOutputFormat(map, "png");
×
564

565
    msInitializeRendererVTable(map->outputformat);
×
566
  }
567
  renderer = MS_MAP_RENDERER(map);
170✔
568

569
  image = msDrawScalebar(map);
170✔
570

571
  if (imageType) {
170✔
572
    map->outputformat =
×
573
        msSelectOutputFormat(map, imageType); /* restore format */
×
574
    msFree(imageType);
×
575
  }
576

577
  if (!image) {
170✔
578
    return MS_FAILURE;
579
  }
580
  embeddedSymbol->pixmap_buffer = calloc(1, sizeof(rasterBufferObj));
169✔
581
  MS_CHECK_ALLOC(embeddedSymbol->pixmap_buffer, sizeof(rasterBufferObj),
169✔
582
                 MS_FAILURE);
583

584
  if (MS_SUCCESS !=
169✔
585
      renderer->getRasterBufferCopy(image, embeddedSymbol->pixmap_buffer)) {
169✔
586
    return MS_FAILURE;
587
  }
588

589
  embeddedSymbol->type = MS_SYMBOL_PIXMAP; /* initialize a few things */
169✔
590
  embeddedSymbol->name = msStrdup("scalebar");
169✔
591
  embeddedSymbol->sizex = embeddedSymbol->pixmap_buffer->width;
169✔
592
  embeddedSymbol->sizey = embeddedSymbol->pixmap_buffer->height;
169✔
593
  if (map->scalebar.transparent) {
169✔
594
    embeddedSymbol->transparent = MS_TRUE;
161✔
595
    embeddedSymbol->transparentcolor = 0;
161✔
596
  }
597

598
  switch (map->scalebar.position) {
169✔
599
  case (MS_LL):
14✔
600
    point.x = MS_NINT(embeddedSymbol->pixmap_buffer->width / 2.0) +
14✔
601
              map->scalebar.offsetx;
14✔
602
    point.y = map->height -
14✔
603
              MS_NINT(embeddedSymbol->pixmap_buffer->height / 2.0) -
14✔
604
              map->scalebar.offsety;
14✔
605
    break;
14✔
606
  case (MS_LR):
155✔
607
    point.x = map->width - MS_NINT(embeddedSymbol->pixmap_buffer->width / 2.0) -
155✔
608
              map->scalebar.offsetx;
155✔
609
    point.y = map->height -
155✔
610
              MS_NINT(embeddedSymbol->pixmap_buffer->height / 2.0) -
155✔
611
              map->scalebar.offsety;
155✔
612
    break;
155✔
613
  case (MS_LC):
×
614
    point.x = MS_NINT(map->width / 2.0) + map->scalebar.offsetx;
×
615
    point.y = map->height -
×
616
              MS_NINT(embeddedSymbol->pixmap_buffer->height / 2.0) -
×
617
              map->scalebar.offsety;
×
618
    break;
×
619
  case (MS_UR):
×
620
    point.x = map->width - MS_NINT(embeddedSymbol->pixmap_buffer->width / 2.0) -
×
621
              map->scalebar.offsetx;
×
622
    point.y = MS_NINT(embeddedSymbol->pixmap_buffer->height / 2.0) +
×
623
              map->scalebar.offsety;
×
624
    break;
×
625
  case (MS_UL):
×
626
    point.x = MS_NINT(embeddedSymbol->pixmap_buffer->width / 2.0) +
×
627
              map->scalebar.offsetx;
×
628
    point.y = MS_NINT(embeddedSymbol->pixmap_buffer->height / 2.0) +
×
629
              map->scalebar.offsety;
×
630
    break;
×
631
  case (MS_UC):
×
632
    point.x = MS_NINT(map->width / 2.0) + map->scalebar.offsetx;
×
633
    point.y = MS_NINT(embeddedSymbol->pixmap_buffer->height / 2.0) +
×
634
              map->scalebar.offsety;
×
635
    break;
×
636
  }
637

638
  l = msGetLayerIndex(map, "__embed__scalebar");
169✔
639
  if (l == -1) {
169✔
640
    if (msGrowMapLayers(map) == NULL)
168✔
641
      return (-1);
642
    l = map->numlayers;
168✔
643
    map->numlayers++;
168✔
644
    if (initLayer((GET_LAYER(map, l)), map) == -1)
168✔
645
      return (-1);
646
    GET_LAYER(map, l)->name = msStrdup("__embed__scalebar");
168✔
647
    GET_LAYER(map, l)->type = MS_LAYER_POINT;
168✔
648

649
    if (msGrowLayerClasses(GET_LAYER(map, l)) == NULL)
168✔
650
      return (-1);
651

652
    if (initClass(GET_LAYER(map, l)->class[0]) == -1)
168✔
653
      return (-1);
654
    GET_LAYER(map, l)->numclasses = 1; /* so we make sure to free it */
168✔
655

656
    /* update the layer order list with the layer's index. */
657
    map->layerorder[l] = l;
168✔
658
  }
659

660
  GET_LAYER(map, l)->status = MS_ON;
169✔
661
  if (map->scalebar.postlabelcache) { /* add it directly to the image */
169✔
662
    if (msMaybeAllocateClassStyle(GET_LAYER(map, l)->class[0], 0) == MS_FAILURE)
155✔
663
      return MS_FAILURE;
664
    GET_LAYER(map, l)->class[0]->styles[0]->symbol = s;
155✔
665
    status = msDrawMarkerSymbol(map, img, &point,
155✔
666
                                GET_LAYER(map, l)->class[0] -> styles[0], 1.0);
667
    if (MS_UNLIKELY(status == MS_FAILURE)) {
155✔
668
      goto embed_cleanup;
×
669
    }
670
  } else {
671
    if (!GET_LAYER(map, l)->class[0] -> labels) {
14✔
672
      if (msGrowClassLabels(GET_LAYER(map, l)->class[0]) == NULL)
14✔
673
        return MS_FAILURE;
674
      initLabel(GET_LAYER(map, l)->class[0] -> labels[0]);
14✔
675
      GET_LAYER(map, l)->class[0]->numlabels = 1;
14✔
676
      GET_LAYER(map, l)->class[0]->labels[0]->force = MS_TRUE;
14✔
677
      GET_LAYER(map, l)->class[0]->labels[0]->size =
14✔
678
          MS_MEDIUM; /* must set a size to have a valid label definition */
679
      GET_LAYER(map, l)->class[0]->labels[0]->priority = MS_MAX_LABEL_PRIORITY;
14✔
680
    }
681
    if (GET_LAYER(map, l)->class[0] -> labels[0] -> numstyles == 0) {
14✔
682
      if (msGrowLabelStyles(GET_LAYER(map, l)->class[0] -> labels[0]) == NULL)
14✔
683
        return (MS_FAILURE);
684
      GET_LAYER(map, l)->class[0]->labels[0]->numstyles = 1;
14✔
685
      initStyle(GET_LAYER(map, l)->class[0] -> labels[0] -> styles[0]);
14✔
686
      GET_LAYER(map, l)->class[0]->labels[0]->styles[0]->_geomtransform.type =
14✔
687
          MS_GEOMTRANSFORM_LABELPOINT;
688
    }
689
    GET_LAYER(map, l)->class[0]->labels[0]->styles[0]->symbol = s;
14✔
690
    status = msAddLabel(map, img, GET_LAYER(map, l)->class[0] -> labels[0], l,
14✔
691
                        0, NULL, &point, -1, NULL);
692
    if (MS_UNLIKELY(status == MS_FAILURE)) {
14✔
693
      goto embed_cleanup;
×
694
    }
695
  }
696

697
embed_cleanup:
14✔
698
  /* Mark layer as deleted so that it doesn't interfere with html legends or
699
   * with saving maps */
700
  GET_LAYER(map, l)->status = MS_DELETE;
169✔
701

702
  msFreeImage(image);
169✔
703
  return status;
169✔
704
}
705

706
/************************************************************************/
707
/* These two functions are used in PHP/Mapscript and Swig/Mapscript     */
708
/************************************************************************/
709

710
/************************************************************************/
711
/*  double GetDeltaExtentsUsingScale(double scale, int units,           */
712
/*                                   double centerLat, int width,       */
713
/*                                   double resolution)                 */
714
/*                                                                      */
715
/*      Utility function to return the maximum extent using the         */
716
/*      scale and the width of the image.                               */
717
/*                                                                      */
718
/*      Base on the function msCalculateScale (mapscale.c)              */
719
/************************************************************************/
720
double GetDeltaExtentsUsingScale(double scale, int units, double centerLat,
8✔
721
                                 int width, double resolution) {
722
  double md = 0.0;
723
  double dfDelta = -1.0;
724

725
  if (scale <= 0 || width <= 0)
8✔
726
    return -1;
727

728
  switch (units) {
8✔
729
  case (MS_DD):
8✔
730
  case (MS_METERS):
731
  case (MS_KILOMETERS):
732
  case (MS_MILES):
733
  case (MS_NAUTICALMILES):
734
  case (MS_INCHES):
735
  case (MS_FEET):
736
    /* remember, we use a pixel-center to pixel-center extent, hence the width-1
737
     */
738
    md = (width - 1) / (resolution * msInchesPerUnit(units, centerLat));
8✔
739
    dfDelta = md * scale;
8✔
740
    break;
8✔
741

742
  default:
743
    break;
744
  }
745

746
  return dfDelta;
747
}
748

749
/************************************************************************/
750
/*    static double Pix2Georef(int nPixPos, int nPixMin, double nPixMax,*/
751
/*                              double dfGeoMin, double dfGeoMax,       */
752
/*                              bool bULisYOrig)                        */
753
/*                                                                      */
754
/*      Utility function to convert a pixel pos to georef pos. If       */
755
/*      bULisYOrig parameter is set to true then the upper left is      */
756
/*      considered to be the Y origin.                                  */
757
/*                                                                      */
758
/************************************************************************/
759
double Pix2Georef(int nPixPos, int nPixMin, int nPixMax, double dfGeoMin,
16,052✔
760
                  double dfGeoMax, int bULisYOrig) {
761
  double dfPosGeo = 0.0;
762

763
  const double dfWidthGeo = dfGeoMax - dfGeoMin;
16,052✔
764
  const int nWidthPix = nPixMax - nPixMin;
16,052✔
765

766
  if (dfWidthGeo > 0.0 && nWidthPix > 0) {
16,052✔
767
    const double dfPixToGeo = dfWidthGeo / (double)nWidthPix;
16,052✔
768

769
    int nDeltaPix;
770
    if (!bULisYOrig)
16,052✔
771
      nDeltaPix = nPixPos - nPixMin;
8,026✔
772
    else
773
      nDeltaPix = nPixMax - nPixPos;
8,026✔
774

775
    const double dfDeltaGeo = nDeltaPix * dfPixToGeo;
16,052✔
776

777
    dfPosGeo = dfGeoMin + dfDeltaGeo;
16,052✔
778
  }
779
  return (dfPosGeo);
16,052✔
780
}
781

782
/* This function converts a pixel value in geo ref. The return value is in
783
 * layer units. This function has been added for the purpose of the ticket #1340
784
 */
785

786
double Pix2LayerGeoref(mapObj *map, layerObj *layer, int value) {
×
787
  double cellsize =
788
      MS_MAX(MS_CELLSIZE(map->extent.minx, map->extent.maxx, map->width),
×
789
             MS_CELLSIZE(map->extent.miny, map->extent.maxy, map->height));
790

791
  double resolutionFactor = map->resolution / map->defresolution;
×
792
  double unitsFactor = 1;
793

794
  if (layer->sizeunits != MS_PIXELS)
×
795
    unitsFactor =
×
796
        msInchesPerUnit(map->units, 0) / msInchesPerUnit(layer->sizeunits, 0);
×
797

798
  return value * cellsize * resolutionFactor * unitsFactor;
×
799
}
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