PostGIS 3.7.0dev-r@@SVN_REVISION@@
Loading...
Searching...
No Matches
lwgeodetic_measures.c
Go to the documentation of this file.
1/*****************************************************************************
2 *
3 * This MobilityDB code is provided under The PostgreSQL License.
4 * Copyright (c) 2016-2023, Université libre de Bruxelles and MobilityDB
5 * contributors
6 *
7 * MobilityDB includes portions of PostGIS version 3 source code released
8 * under the GNU General Public License (GPLv2 or later).
9 * Copyright (c) 2001-2023, PostGIS contributors
10 *
11 * Permission to use, copy, modify, and distribute this software and its
12 * documentation for any purpose, without fee, and without a written
13 * agreement is hereby granted, provided that the above copyright notice and
14 * this paragraph and the following two paragraphs appear in all copies.
15 *
16 * IN NO EVENT SHALL UNIVERSITE LIBRE DE BRUXELLES BE LIABLE TO ANY PARTY FOR
17 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
18 * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION,
19 * EVEN IF UNIVERSITE LIBRE DE BRUXELLES HAS BEEN ADVISED OF THE POSSIBILITY
20 * OF SUCH DAMAGE.
21 *
22 * UNIVERSITE LIBRE DE BRUXELLES SPECIFICALLY DISCLAIMS ANY WARRANTIES,
23 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
24 * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON
25 * AN "AS IS" BASIS, AND UNIVERSITE LIBRE DE BRUXELLES HAS NO OBLIGATIONS TO
26 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
27 *
28 *****************************************************************************/
29
39// #include "point/geography_funcs.h"
40
41/* C */
42#include <float.h>
43/* PostGIS */
44#include <liblwgeom_internal.h>
45#include <lwgeodetic_tree.h>
46
47/*****************************************************************************/
48
49
50/***********************************************************************
51 * ST_LineSubstring for geographies
52 ***********************************************************************/
53
59static void
61 const POINT4D *p1, const POINT4D *p2, POINT4D *p,
62 const SPHEROID *s, double f)
63
64{
65 GEOGRAPHIC_POINT g, g1, g2;
66 geographic_point_init(p1->x, p1->y, &g1);
67 geographic_point_init(p2->x, p2->y, &g2);
68 int success;
69 double dist, dir;
70
71 /* Special sphere case */
72 if ( s == NULL || s->a == s->b )
73 {
74 /* Calculate distance and direction between g1 and g2 */
75 dist = sphere_distance(&g1, &g2);
76 dir = sphere_direction(&g1, &g2, dist);
77 /* Compute interpolation point */
78 success = sphere_project(&g1, dist*f, dir, &g);
79 }
80 /* Spheroid case */
81 else
82 {
83 /* Calculate distance and direction between g1 and g2 */
84 dist = spheroid_distance(&g1, &g2, s);
85 dir = spheroid_direction(&g1, &g2, s);
86 /* Compute interpolation point */
87 success = spheroid_project(&g1, s, dist*f, dir, &g);
88 }
89
90 /* If success, use newly computed lat and lon,
91 * otherwise return precomputed cartesian result */
92 if (success == LW_SUCCESS)
93 {
96 }
97}
98
99
103LWGEOM *
105 const LWLINE *lwline,
106 const SPHEROID *s,
107 double from, double to,
108 double tolerance)
109{
110 POINTARRAY *dpa;
111 POINTARRAY *ipa = lwline->points;
112 LWGEOM *lwresult;
113 POINT4D pt;
114 POINT4D p1, p2;
115 GEOGRAPHIC_POINT g1, g2;
116 int nsegs, i;
117 double length, slength, tlength;
118 int state = 0; /* 0 = before, 1 = inside */
119 uint32_t srid = lwline->srid;
120
121 /*
122 * Create a dynamic pointarray with an initial capacity
123 * equal to full copy of input points
124 */
126 (char) FLAGS_GET_Z(ipa->flags),
127 (char) FLAGS_GET_M(ipa->flags),
128 ipa->npoints);
129
130 /* Compute total line length */
131 length = ptarray_length_spheroid(ipa, s);
132
133 /* Get 'from' and 'to' lengths */
134 from = length * from;
135 to = length * to;
136 tlength = 0;
137 getPoint4d_p(ipa, 0, &p1);
138 geographic_point_init(p1.x, p1.y, &g1);
139 nsegs = ipa->npoints - 1;
140 for (i = 0; i < nsegs; i++)
141 {
142 double dseg;
143 getPoint4d_p(ipa, (uint32_t) i+1, &p2);
144 geographic_point_init(p2.x, p2.y, &g2);
145
146 /* Find the length of this segment */
147 /* Special sphere case */
148 if ( s->a == s->b )
149 slength = s->radius * sphere_distance(&g1, &g2);
150 /* Spheroid case */
151 else
152 slength = spheroid_distance(&g1, &g2, s);
153
154 /* We are before requested start. */
155 if (state == 0) /* before */
156 {
157 if (fabs ( from - ( tlength + slength ) ) <= tolerance)
158 {
159 /* Second point is our start */
161 state = 1; /* we're inside now */
162 goto END;
163 }
164 else if (fabs(from - tlength) <= tolerance)
165 {
166 /* First point is our start */
168 /*
169 * We're inside now, but will check
170 * 'to' point as well
171 */
172 state = 1;
173 }
174 /*
175 * Didn't reach the 'from' point,
176 * nothing to do
177 */
178 else if (from > tlength + slength)
179 {
180 goto END;
181 }
182 else /* tlength < from < tlength+slength */
183 {
184 /* Our start is between first and second point */
185 dseg = (from - tlength) / slength;
186 interpolate_point4d_spheroid(&p1, &p2, &pt, s, dseg);
188 /* We're inside now, but will check 'to' point as well */
189 state = 1;
190 }
191 }
192
193 if (state == 1) /* inside */
194 {
195 /* 'to' point is our second point. */
196 if (fabs(to - ( tlength + slength ) ) <= tolerance )
197 {
199 break; /* substring complete */
200 }
201 /*
202 * 'to' point is our first point.
203 * (should only happen if 'to' is 0)
204 */
205 else if (fabs(to - tlength) <= tolerance)
206 {
208 break; /* substring complete */
209 }
210 /*
211 * Didn't reach the 'end' point,
212 * just copy second point
213 */
214 else if (to > tlength + slength)
215 {
217 goto END;
218 }
219 /*
220 * 'to' point falls on this segment
221 * Interpolate and break.
222 */
223 else if (to < tlength + slength )
224 {
225 dseg = (to - tlength) / slength;
226 interpolate_point4d_spheroid(&p1, &p2, &pt, s, dseg);
228 break;
229 }
230 }
231
232 END:
233 tlength += slength;
234 memcpy(&p1, &p2, sizeof(POINT4D));
235 geographic_point_init(p1.x, p1.y, &g1);
236 }
237
238 if (dpa->npoints <= 1) {
239 lwresult = lwpoint_as_lwgeom(lwpoint_construct(srid, NULL, dpa));
240 }
241 else {
242 lwresult = lwline_as_lwgeom(lwline_construct(srid, NULL, dpa));
243 }
244
245 return lwresult;
246}
247
248
249/***********************************************************************
250 * Interpolate a point along a geographic line.
251 ***********************************************************************/
252
256LWGEOM *
258 const LWLINE *line, double length_fraction,
259 const SPHEROID *s, char repeat)
260{
261 POINT4D pt;
262 uint32_t i;
263 uint32_t points_to_interpolate;
264 uint32_t points_found = 0;
265 double length;
266 double length_fraction_increment = length_fraction;
267 double length_fraction_consumed = 0;
268 char has_z = (char) lwgeom_has_z(lwline_as_lwgeom(line));
269 char has_m = (char) lwgeom_has_m(lwline_as_lwgeom(line));
270 const POINTARRAY* ipa = line->points;
271 POINTARRAY *opa;
272 POINT4D p1, p2;
273 POINT3D q1, q2;
274 LWGEOM *lwresult;
275 GEOGRAPHIC_POINT g1, g2;
276 uint32_t srid = line->srid;
277
278 /* Empty.InterpolatePoint == Point Empty */
279 if ( lwline_is_empty(line) )
280 {
282 }
283
284 /*
285 * If distance is one of the two extremes, return the point on that
286 * end rather than doing any computations
287 */
288 if ( length_fraction == 0.0 || length_fraction == 1.0 )
289 {
290 if ( length_fraction == 0.0 )
291 getPoint4d_p(ipa, 0, &pt);
292 else
293 getPoint4d_p(ipa, ipa->npoints-1, &pt);
294
295 opa = ptarray_construct(has_z, has_m, 1);
296 ptarray_set_point4d(opa, 0, &pt);
297 return lwpoint_as_lwgeom(lwpoint_construct(srid, NULL, opa));
298 }
299
300 /* Interpolate points along the line */
301 length = ptarray_length_spheroid(ipa, s);
302 points_to_interpolate = repeat ? (uint32_t) floor(1 / length_fraction) : 1;
303 opa = ptarray_construct(has_z, has_m, points_to_interpolate);
304
305 getPoint4d_p(ipa, 0, &p1);
306 geographic_point_init(p1.x, p1.y, &g1);
307 for ( i = 0; i < ipa->npoints - 1 && points_found < points_to_interpolate; i++ )
308 {
309 double segment_length_frac;
310 getPoint4d_p(ipa, i+1, &p2);
311 geographic_point_init(p2.x, p2.y, &g2);
312
313 /* Special sphere case */
314 if ( s->a == s->b )
315 segment_length_frac = s->radius * sphere_distance(&g1, &g2) / length;
316 /* Spheroid case */
317 else
318 segment_length_frac = spheroid_distance(&g1, &g2, s) / length;
319
320 /* If our target distance is before the total length we've seen
321 * so far. create a new point some distance down the current
322 * segment.
323 */
324 while ( length_fraction < length_fraction_consumed + segment_length_frac && points_found < points_to_interpolate )
325 {
326 geog2cart(&g1, &q1);
327 geog2cart(&g2, &q2);
328 double segment_fraction = (length_fraction - length_fraction_consumed) / segment_length_frac;
329 interpolate_point4d_spheroid(&p1, &p2, &pt, s, segment_fraction);
330 ptarray_set_point4d(opa, points_found++, &pt);
331 length_fraction += length_fraction_increment;
332 }
333
334 length_fraction_consumed += segment_length_frac;
335
336 p1 = p2;
337 g1 = g2;
338 }
339
340 /* Return the last point on the line. This shouldn't happen, but
341 * could if there's some floating point rounding errors. */
342 if (points_found < points_to_interpolate)
343 {
344 getPoint4d_p(ipa, ipa->npoints - 1, &pt);
345 ptarray_set_point4d(opa, points_found, &pt);
346 }
347
348 if (opa->npoints <= 1)
349 {
350 lwresult = lwpoint_as_lwgeom(lwpoint_construct(srid, NULL, opa));
351 } else {
352 lwresult = lwmpoint_as_lwgeom(lwmpoint_construct(srid, opa));
353 }
354
355 return lwresult;
356}
357
361double
363 const POINTARRAY *pa,
364 const POINT4D *p4d,
365 const SPHEROID *s,
366 double tolerance,
367 double *mindistout,
368 POINT4D *proj4d)
369{
371 GEOGRAPHIC_POINT a, b, nearest = {0}; /* make compiler quiet */
372 POINT4D p1, p2;
373 const POINT2D *p;
374 POINT2D proj;
375 uint32_t i, seg = 0;
376 int use_sphere = (s->a == s->b ? 1 : 0);
377 int hasz;
378 double za = 0.0, zb = 0.0;
379 double distance,
380 length, /* Used for computing lengths */
381 seglength = 0.0, /* length of the segment where the closest point is located */
382 partlength = 0.0, /* length from the beginning of the point array to the closest point */
383 totlength = 0.0; /* length of the point array */
384
385 /* Initialize our point */
386 geographic_point_init(p4d->x, p4d->y, &a);
387
388 /* Handle point/point case here */
389 if ( pa->npoints <= 1)
390 {
391 double mindist = 0.0;
392 if ( pa->npoints == 1 )
393 {
394 p = getPoint2d_cp(pa, 0);
395 geographic_point_init(p->x, p->y, &b);
396 /* Sphere special case, axes equal */
397 mindist = s->radius * sphere_distance(&a, &b);
398 /* If close or greater than tolerance, get the real answer to be sure */
399 if ( ! use_sphere || mindist > 0.95 * tolerance )
400 {
401 mindist = spheroid_distance(&a, &b, s);
402 }
403 }
404 if ( mindistout ) *mindistout = mindist;
405 return 0.0;
406 }
407
408 /* Make result really big, so that everything will be smaller than it */
409 distance = FLT_MAX;
410
411 /* Initialize start of line */
412 p = getPoint2d_cp(pa, 0);
413 geographic_point_init(p->x, p->y, &(e.start));
414
415 /* Iterate through the edges in our line */
416 for ( i = 1; i < pa->npoints; i++ )
417 {
418 double d;
419 p = getPoint2d_cp(pa, i);
420 geographic_point_init(p->x, p->y, &(e.end));
421 /* Get the spherical distance between point and edge */
422 d = s->radius * edge_distance_to_point(&e, &a, &b);
423 /* New shortest distance! Record this distance / location / segment */
424 if ( d < distance )
425 {
426 distance = d;
427 nearest = b;
428 seg = i - 1;
429 }
430 /* We've gotten closer than the tolerance... */
431 if ( d < tolerance )
432 {
433 /* Working on a sphere? The answer is correct, return */
434 if ( use_sphere )
435 {
436 break;
437 }
438 /* Far enough past the tolerance that the spheroid calculation won't change things */
439 else if ( d < tolerance * 0.95 )
440 {
441 break;
442 }
443 /* On a spheroid and near the tolerance? Confirm that we are *actually* closer than tolerance */
444 else
445 {
446 d = spheroid_distance(&a, &nearest, s);
447 /* Yes, closer than tolerance, return! */
448 if ( d < tolerance )
449 break;
450 }
451 }
452 e.start = e.end;
453 }
454
455 if ( mindistout ) *mindistout = distance;
456
457 /* See if we have a third dimension */
458 hasz = (bool) FLAGS_GET_Z(pa->flags);
459
460 /* Initialize first point of array */
461 getPoint4d_p(pa, 0, &p1);
462 geographic_point_init(p1.x, p1.y, &a);
463 if ( hasz )
464 za = p1.z;
465
466 /* Loop and sum the length for each segment */
467 for ( i = 1; i < pa->npoints; i++ )
468 {
469 getPoint4d_p(pa, i, &p1);
470 geographic_point_init(p1.x, p1.y, &b);
471 if ( hasz )
472 zb = p1.z;
473
474 /* Special sphere case */
475 if ( s->a == s->b )
476 length = s->radius * sphere_distance(&a, &b);
477 /* Spheroid case */
478 else
479 length = spheroid_distance(&a, &b, s);
480
481 /* Add in the vertical displacement if we're in 3D */
482 if ( hasz )
483 length = sqrt( (zb-za)*(zb-za) + length*length );
484
485 /* Add this segment length to the total length */
486 totlength += length;
487
488 /* Add this segment length to the partial length */
489 if (i - 1 < seg)
490 partlength += length;
491 else if (i - 1 == seg)
492 /* Save segment length for computing the final value of partlength */
493 seglength = length;
494
495 /* B gets incremented in the next loop, so we save the value here */
496 a = b;
497 za = zb;
498 }
499
500 /* Copy nearest into 2D/4D holder */
501 proj4d->x = proj.x = rad2deg(nearest.lon);
502 proj4d->y = proj.y = rad2deg(nearest.lat);
503
504 /* Compute distance from beginning of the segment to closest point */
505
506 /* Start of the segment */
507 getPoint4d_p(pa, seg, &p1);
508 geographic_point_init(p1.x, p1.y, &a);
509
510 /* Closest point */
511 geographic_point_init(proj4d->x, proj4d->y, &b);
512
513 /* Special sphere case */
514 if ( s->a == s->b )
515 length = s->radius * sphere_distance(&a, &b);
516 /* Spheroid case */
517 else
518 length = spheroid_distance(&a, &b, s);
519
520 if ( hasz )
521 {
522 /* Compute Z and M values for closest point */
523 double f = length / seglength;
524 getPoint4d_p(pa, seg + 1, &p2);
525 proj4d->z = p1.z + ((p2.z - p1.z) * f);
526 proj4d->m = p1.m + ((p2.m - p1.m) * f);
527 /* Add in the vertical displacement if we're in 3D */
528 za = p1.z;
529 zb = proj4d->z;
530 length = sqrt( (zb-za)*(zb-za) + length*length );
531 }
532
533 /* Add this segment length to the total */
534 partlength += length;
535
536 /* Location of any point on a zero-length line is 0 */
537 /* See http://trac.osgeo.org/postgis/ticket/1772#comment:2 */
538 if ( partlength == 0 || totlength == 0 )
539 return 0.0;
540
541 /* For robustness, force 0 when closest point == startpoint */
542 p = getPoint2d_cp(pa, 0);
543 if ( seg == 0 && p2d_same(&proj, p) )
544 return 0.0;
545
546 /* For robustness, force 1 when closest point == endpoint */
547 p = getPoint2d_cp(pa, pa->npoints - 1);
548 if ( (seg >= (pa->npoints-2)) && p2d_same(&proj, p) )
549 return 1.0;
550
551 /* Floating point arithmetic is not reliable, make sure we return values [0,1] */
552 double result = partlength / totlength;
553 if ( result < 0.0 ) {
554 result = 0.0;
555 } else if ( result > 1.0 ) {
556 result = 1.0;
557 }
558 return result;
559}
560
561/*****************************************************************************/
char * s
Definition cu_in_wkt.c:23
char result[OUT_DOUBLE_BUFFER_SIZE]
Definition cu_print.c:267
LWGEOM * lwmpoint_as_lwgeom(const LWMPOINT *obj)
Definition lwgeom.c:332
LWGEOM * lwpoint_as_lwgeom(const LWPOINT *obj)
Definition lwgeom.c:372
#define LW_FALSE
Definition liblwgeom.h:94
LWMPOINT * lwmpoint_construct(int32_t srid, const POINTARRAY *pa)
Definition lwmpoint.c:52
#define LW_SUCCESS
Definition liblwgeom.h:97
LWPOINT * lwpoint_construct(int32_t srid, GBOX *bbox, POINTARRAY *point)
Definition lwpoint.c:129
POINTARRAY * ptarray_construct_empty(char hasz, char hasm, uint32_t maxpoints)
Create a new POINTARRAY with no points.
Definition ptarray.c:59
int lwgeom_has_z(const LWGEOM *geom)
Return LW_TRUE if geometry has Z ordinates.
Definition lwgeom.c:962
#define FLAGS_GET_Z(flags)
Definition liblwgeom.h:165
LWLINE * lwline_construct(int32_t srid, GBOX *bbox, POINTARRAY *points)
Definition lwline.c:42
LWGEOM * lwline_as_lwgeom(const LWLINE *obj)
Definition lwgeom.c:367
#define FLAGS_GET_M(flags)
Definition liblwgeom.h:166
int getPoint4d_p(const POINTARRAY *pa, uint32_t n, POINT4D *point)
Definition lwgeom_api.c:125
int ptarray_append_point(POINTARRAY *pa, const POINT4D *pt, int allow_duplicates)
Append a point to the end of an existing POINTARRAY If allow_duplicate is LW_FALSE,...
Definition ptarray.c:147
int lwgeom_has_m(const LWGEOM *geom)
Return LW_TRUE if geometry has M ordinates.
Definition lwgeom.c:969
void ptarray_set_point4d(POINTARRAY *pa, uint32_t n, const POINT4D *p4d)
Definition lwgeom_api.c:369
POINTARRAY * ptarray_construct(char hasz, char hasm, uint32_t npoints)
Construct an empty pointarray, allocating storage and setting the npoints, but not filling in any inf...
Definition ptarray.c:51
LWGEOM * lwgeom_clone_deep(const LWGEOM *lwgeom)
Deep clone an LWGEOM, everything is copied.
Definition lwgeom.c:557
int lwline_is_empty(const LWLINE *line)
int p2d_same(const POINT2D *p1, const POINT2D *p2)
Definition lwalgorithm.c:57
double longitude_radians_normalize(double lon)
Convert a longitude to the range of -PI,PI.
Definition lwgeodetic.c:50
double latitude_radians_normalize(double lat)
Convert a latitude to the range of -PI/2,PI/2.
Definition lwgeodetic.c:78
int sphere_project(const GEOGRAPHIC_POINT *r, double distance, double azimuth, GEOGRAPHIC_POINT *n)
Given a starting location r, a distance and an azimuth to the new point, compute the location of the ...
void geographic_point_init(double lon, double lat, GEOGRAPHIC_POINT *g)
Initialize a geographic point.
Definition lwgeodetic.c:180
double ptarray_length_spheroid(const POINTARRAY *pa, const SPHEROID *s)
double sphere_distance(const GEOGRAPHIC_POINT *s, const GEOGRAPHIC_POINT *e)
Given two points on a unit sphere, calculate their distance apart in radians.
Definition lwgeodetic.c:896
double sphere_direction(const GEOGRAPHIC_POINT *s, const GEOGRAPHIC_POINT *e, double d)
Given two points on a unit sphere, calculate the direction from s to e.
Definition lwgeodetic.c:927
double edge_distance_to_point(const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *gp, GEOGRAPHIC_POINT *closest)
void geog2cart(const GEOGRAPHIC_POINT *g, POINT3D *p)
Convert spherical coordinates to cartesian coordinates on unit sphere.
Definition lwgeodetic.c:404
#define rad2deg(r)
Definition lwgeodetic.h:81
double spheroid_distance(const GEOGRAPHIC_POINT *a, const GEOGRAPHIC_POINT *b, const SPHEROID *spheroid)
Computes the shortest distance along the surface of the spheroid between two points,...
Definition lwspheroid.c:79
int spheroid_project(const GEOGRAPHIC_POINT *r, const SPHEROID *spheroid, double distance, double azimuth, GEOGRAPHIC_POINT *g)
Given a location, an azimuth and a distance, computes the location of the projected point.
Definition lwspheroid.c:128
double spheroid_direction(const GEOGRAPHIC_POINT *r, const GEOGRAPHIC_POINT *s, const SPHEROID *spheroid)
Computes the forward azimuth of the geodesic joining two points on the spheroid, using the inverse ge...
Definition lwspheroid.c:105
double ptarray_locate_point_spheroid(const POINTARRAY *pa, const POINT4D *p4d, const SPHEROID *s, double tolerance, double *mindistout, POINT4D *proj4d)
Locate a point along the point array defining a geographic line.
LWGEOM * geography_interpolate_points(const LWLINE *line, double length_fraction, const SPHEROID *s, char repeat)
Interpolate a point along a geographic line.
LWGEOM * geography_substring(const LWLINE *lwline, const SPHEROID *s, double from, double to, double tolerance)
Return the part of a line between two fractional locations.
static void interpolate_point4d_spheroid(const POINT4D *p1, const POINT4D *p2, POINT4D *p, const SPHEROID *s, double f)
Find interpolation point p between geography points p1 and p2 so that the len(p1,p) == len(p1,...
static const POINT2D * getPoint2d_cp(const POINTARRAY *pa, uint32_t n)
Returns a POINT2D pointer into the POINTARRAY serialized_ptlist, suitable for reading from.
Definition lwinline.h:97
static double distance(double x1, double y1, double x2, double y2)
Definition lwtree.c:1032
GEOGRAPHIC_POINT start
Definition lwgeodetic.h:64
GEOGRAPHIC_POINT end
Definition lwgeodetic.h:65
Two-point great circle segment from a to b.
Definition lwgeodetic.h:63
Point in spherical coordinates on the world.
Definition lwgeodetic.h:54
POINTARRAY * points
Definition liblwgeom.h:483
int32_t srid
Definition liblwgeom.h:484
double y
Definition liblwgeom.h:390
double x
Definition liblwgeom.h:390
double m
Definition liblwgeom.h:414
double x
Definition liblwgeom.h:414
double z
Definition liblwgeom.h:414
double y
Definition liblwgeom.h:414
lwflags_t flags
Definition liblwgeom.h:431
uint32_t npoints
Definition liblwgeom.h:427