geo_coder 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. data/Gemfile +12 -0
  2. data/Gemfile.lock +32 -0
  3. data/History.txt +6 -0
  4. data/Makefile +13 -0
  5. data/Manifest.txt +18 -0
  6. data/README.rdoc +197 -0
  7. data/Rakefile +53 -0
  8. data/TODO.txt +8 -0
  9. data/VERSION +1 -0
  10. data/bin/build_indexes +8 -0
  11. data/bin/rebuild_cluster +22 -0
  12. data/bin/rebuild_metaphones +23 -0
  13. data/bin/tiger_import +59 -0
  14. data/demos/demo/app/ext/geocodewrap.rb +84 -0
  15. data/demos/demo/app/views/index.builder +13 -0
  16. data/demos/demo/app/views/index.erb +71 -0
  17. data/demos/demo/config.ru +12 -0
  18. data/demos/demo/config/bootstraps.rb +130 -0
  19. data/demos/demo/config/geoenvironment.rb +25 -0
  20. data/demos/demo/geocoder_helper.rb +12 -0
  21. data/demos/demo/geocom_geocode.rb +10 -0
  22. data/demos/demo/main.rb +3 -0
  23. data/demos/demo/rakefile.rb +17 -0
  24. data/demos/demo/tmp/restart.txt +0 -0
  25. data/demos/simpledemo/views/index.builder +13 -0
  26. data/demos/simpledemo/views/index.erb +69 -0
  27. data/demos/simpledemo/ws.rb +83 -0
  28. data/doc/Makefile +7 -0
  29. data/doc/html4css1.css +279 -0
  30. data/doc/lookup.rst +193 -0
  31. data/doc/parsing.rst +125 -0
  32. data/doc/voidspace.css +147 -0
  33. data/geo_coder.gemspec +172 -0
  34. data/lib/geocoder/us.rb +21 -0
  35. data/lib/geocoder/us/address.rb +290 -0
  36. data/lib/geocoder/us/constants.rb +670 -0
  37. data/lib/geocoder/us/database.rb +745 -0
  38. data/lib/geocoder/us/import.rb +181 -0
  39. data/lib/geocoder/us/import/tiger.rb +13 -0
  40. data/lib/geocoder/us/numbers.rb +58 -0
  41. data/navteq/README +4 -0
  42. data/navteq/convert.sql +37 -0
  43. data/navteq/navteq_import +39 -0
  44. data/navteq/prepare.sql +92 -0
  45. data/sql/cluster.sql +16 -0
  46. data/sql/convert.sql +80 -0
  47. data/sql/create.sql +37 -0
  48. data/sql/index.sql +12 -0
  49. data/sql/place.csv +104944 -0
  50. data/sql/place.sql +104948 -0
  51. data/sql/setup.sql +78 -0
  52. data/src/Makefile +13 -0
  53. data/src/README +14 -0
  54. data/src/liblwgeom/Makefile +75 -0
  55. data/src/liblwgeom/box2d.c +54 -0
  56. data/src/liblwgeom/lex.yy.c +4799 -0
  57. data/src/liblwgeom/liblwgeom.h +1405 -0
  58. data/src/liblwgeom/lwalgorithm.c +946 -0
  59. data/src/liblwgeom/lwalgorithm.h +52 -0
  60. data/src/liblwgeom/lwcircstring.c +759 -0
  61. data/src/liblwgeom/lwcollection.c +541 -0
  62. data/src/liblwgeom/lwcompound.c +118 -0
  63. data/src/liblwgeom/lwcurvepoly.c +86 -0
  64. data/src/liblwgeom/lwgeom.c +886 -0
  65. data/src/liblwgeom/lwgeom_api.c +2201 -0
  66. data/src/liblwgeom/lwgparse.c +1219 -0
  67. data/src/liblwgeom/lwgunparse.c +1054 -0
  68. data/src/liblwgeom/lwline.c +525 -0
  69. data/src/liblwgeom/lwmcurve.c +125 -0
  70. data/src/liblwgeom/lwmline.c +137 -0
  71. data/src/liblwgeom/lwmpoint.c +138 -0
  72. data/src/liblwgeom/lwmpoly.c +141 -0
  73. data/src/liblwgeom/lwmsurface.c +129 -0
  74. data/src/liblwgeom/lwpoint.c +439 -0
  75. data/src/liblwgeom/lwpoly.c +579 -0
  76. data/src/liblwgeom/lwsegmentize.c +1047 -0
  77. data/src/liblwgeom/lwutil.c +369 -0
  78. data/src/liblwgeom/measures.c +861 -0
  79. data/src/liblwgeom/postgis_config.h +93 -0
  80. data/src/liblwgeom/ptarray.c +847 -0
  81. data/src/liblwgeom/vsprintf.c +179 -0
  82. data/src/liblwgeom/wktparse.h +126 -0
  83. data/src/liblwgeom/wktparse.lex +74 -0
  84. data/src/liblwgeom/wktparse.tab.c +2353 -0
  85. data/src/liblwgeom/wktparse.tab.h +145 -0
  86. data/src/liblwgeom/wktparse.y +385 -0
  87. data/src/libsqlite3_geocoder/Makefile +22 -0
  88. data/src/libsqlite3_geocoder/Makefile.nix +15 -0
  89. data/src/libsqlite3_geocoder/Makefile.redhat +15 -0
  90. data/src/libsqlite3_geocoder/extension.c +121 -0
  91. data/src/libsqlite3_geocoder/extension.h +13 -0
  92. data/src/libsqlite3_geocoder/levenshtein.c +42 -0
  93. data/src/libsqlite3_geocoder/metaphon.c +278 -0
  94. data/src/libsqlite3_geocoder/util.c +37 -0
  95. data/src/libsqlite3_geocoder/wkb_compress.c +54 -0
  96. data/src/metaphone/Makefile +7 -0
  97. data/src/metaphone/README +49 -0
  98. data/src/metaphone/extension.c +37 -0
  99. data/src/metaphone/metaphon.c +251 -0
  100. data/src/shp2sqlite/Makefile +37 -0
  101. data/src/shp2sqlite/Makefile.nix +36 -0
  102. data/src/shp2sqlite/Makefile.redhat +35 -0
  103. data/src/shp2sqlite/dbfopen.c +1595 -0
  104. data/src/shp2sqlite/getopt.c +695 -0
  105. data/src/shp2sqlite/getopt.h +127 -0
  106. data/src/shp2sqlite/shapefil.h +500 -0
  107. data/src/shp2sqlite/shp2sqlite.c +1974 -0
  108. data/src/shp2sqlite/shpopen.c +1894 -0
  109. data/tests/address.rb +236 -0
  110. data/tests/benchmark.rb +20 -0
  111. data/tests/constants.rb +57 -0
  112. data/tests/data/address-sample.csv +52 -0
  113. data/tests/data/db-test.csv +57 -0
  114. data/tests/data/locations.csv +4 -0
  115. data/tests/database.rb +137 -0
  116. data/tests/generate.rb +34 -0
  117. data/tests/numbers.rb +46 -0
  118. data/tests/run.rb +11 -0
  119. metadata +237 -0
@@ -0,0 +1,369 @@
1
+ #include <stdio.h>
2
+ #include <stdlib.h>
3
+ #include <stdarg.h>
4
+ #include <string.h>
5
+
6
+
7
+ /* Global variables */
8
+ #include "liblwgeom.h"
9
+
10
+ void *init_allocator(size_t size);
11
+ void init_freeor(void *mem);
12
+ void *init_reallocator(void *mem, size_t size);
13
+ void init_noticereporter(const char *fmt, va_list ap);
14
+ void init_errorreporter(const char *fmt, va_list ap);
15
+
16
+ lwallocator lwalloc_var = init_allocator;
17
+ lwreallocator lwrealloc_var = init_reallocator;
18
+ lwfreeor lwfree_var = init_freeor;
19
+ lwreporter lwnotice_var = init_noticereporter;
20
+ lwreporter lwerror_var = init_errorreporter;
21
+
22
+ static char *lwgeomTypeName[] = {
23
+ "Unknown",
24
+ "Point",
25
+ "Line",
26
+ "Polygon",
27
+ "MultiPoint",
28
+ "MultiLine",
29
+ "MultiPolygon",
30
+ "GeometryCollection",
31
+ "CircularString",
32
+ "CompoundString",
33
+ "Invalid Type", /* POINTTYPEI */
34
+ "Invalid Type", /* LINETYPEI */
35
+ "Invalid Type", /* POLYTYPEI */
36
+ "CurvePolygon",
37
+ "MultiCurve",
38
+ "MultiSurface"
39
+ };
40
+
41
+
42
+ /*
43
+ * lwnotice/lwerror handlers
44
+ *
45
+ * Since variadic functions cannot pass their parameters directly, we need
46
+ * wrappers for these functions to convert the arguments into a va_list
47
+ * structure.
48
+ */
49
+
50
+ void
51
+ lwnotice(const char *fmt, ...)
52
+ {
53
+ va_list ap;
54
+
55
+ va_start(ap, fmt);
56
+
57
+ /* Call the supplied function */
58
+ (*lwnotice_var)(fmt, ap);
59
+
60
+ va_end(ap);
61
+ }
62
+
63
+ void
64
+ lwerror(const char *fmt, ...)
65
+ {
66
+ va_list ap;
67
+
68
+ va_start(ap, fmt);
69
+
70
+ /* Call the supplied function */
71
+ (*lwerror_var)(fmt, ap);
72
+
73
+ va_end(ap);
74
+ }
75
+
76
+ /*
77
+ * Initialisation allocators
78
+ *
79
+ * These are used the first time any of the allocators are called
80
+ * to enable executables/libraries that link into liblwgeom to
81
+ * be able to set up their own allocators. This is mainly useful
82
+ * for older PostgreSQL versions that don't have functions that
83
+ * are called upon startup.
84
+ */
85
+
86
+ void *
87
+ init_allocator(size_t size)
88
+ {
89
+ lwgeom_init_allocators();
90
+
91
+ return lwalloc_var(size);
92
+ }
93
+
94
+ void
95
+ init_freeor(void *mem)
96
+ {
97
+ lwgeom_init_allocators();
98
+
99
+ lwfree_var(mem);
100
+ }
101
+
102
+ void *
103
+ init_reallocator(void *mem, size_t size)
104
+ {
105
+ lwgeom_init_allocators();
106
+
107
+ return lwrealloc_var(mem, size);
108
+ }
109
+
110
+ void
111
+ init_noticereporter(const char *fmt, va_list ap)
112
+ {
113
+ lwgeom_init_allocators();
114
+
115
+ (*lwnotice_var)(fmt, ap);
116
+ }
117
+
118
+ void
119
+ init_errorreporter(const char *fmt, va_list ap)
120
+ {
121
+ lwgeom_init_allocators();
122
+
123
+ (*lwerror_var)(fmt, ap);
124
+ }
125
+
126
+
127
+ /*
128
+ * Default allocators
129
+ *
130
+ * We include some default allocators that use malloc/free/realloc
131
+ * along with stdout/stderr since this is the most common use case
132
+ *
133
+ */
134
+
135
+ void *
136
+ default_allocator(size_t size)
137
+ {
138
+ void *mem = malloc(size);
139
+ return mem;
140
+ }
141
+
142
+ void
143
+ default_freeor(void *mem)
144
+ {
145
+ free(mem);
146
+ }
147
+
148
+ void *
149
+ default_reallocator(void *mem, size_t size)
150
+ {
151
+ void *ret = realloc(mem, size);
152
+ return ret;
153
+ }
154
+
155
+ void
156
+ default_noticereporter(const char *fmt, va_list ap)
157
+ {
158
+ char *msg;
159
+
160
+ /*
161
+ * This is a GNU extension.
162
+ * Dunno how to handle errors here.
163
+ */
164
+ if (!lw_vasprintf (&msg, fmt, ap))
165
+ {
166
+ va_end (ap);
167
+ return;
168
+ }
169
+ printf("%s\n", msg);
170
+ free(msg);
171
+ }
172
+
173
+ void
174
+ default_errorreporter(const char *fmt, va_list ap)
175
+ {
176
+ char *msg;
177
+
178
+ /*
179
+ * This is a GNU extension.
180
+ * Dunno how to handle errors here.
181
+ */
182
+ if (!lw_vasprintf (&msg, fmt, ap))
183
+ {
184
+ va_end (ap);
185
+ return;
186
+ }
187
+ fprintf(stderr, "%s\n", msg);
188
+ free(msg);
189
+ exit(1);
190
+ }
191
+
192
+
193
+ /*
194
+ * This function should be called from lwgeom_init_allocators() by programs
195
+ * which wish to use the default allocators above
196
+ */
197
+
198
+ void lwgeom_install_default_allocators(void)
199
+ {
200
+ lwalloc_var = default_allocator;
201
+ lwrealloc_var = default_reallocator;
202
+ lwfree_var = default_freeor;
203
+ lwerror_var = default_errorreporter;
204
+ lwnotice_var = default_noticereporter;
205
+ }
206
+
207
+
208
+ const char *
209
+ lwgeom_typename(int type)
210
+ {
211
+ /* something went wrong somewhere */
212
+ if ( type < 0 || type > 15 ) {
213
+ /* assert(0); */
214
+ return "Invalid type";
215
+ }
216
+ return lwgeomTypeName[type];
217
+ }
218
+
219
+ void *
220
+ lwalloc(size_t size)
221
+ {
222
+ void *mem = lwalloc_var(size);
223
+ LWDEBUGF(5, "lwalloc: %d@%p", size, mem);
224
+ return mem;
225
+ }
226
+
227
+ void *
228
+ lwrealloc(void *mem, size_t size)
229
+ {
230
+ LWDEBUGF(5, "lwrealloc: %d@%p", size, mem);
231
+ return lwrealloc_var(mem, size);
232
+ }
233
+
234
+ void
235
+ lwfree(void *mem)
236
+ {
237
+ lwfree_var(mem);
238
+ }
239
+
240
+ /*
241
+ * Removes trailing zeros and dot for a %f formatted number.
242
+ * Modifies input.
243
+ */
244
+ void
245
+ trim_trailing_zeros(char *str)
246
+ {
247
+ char *ptr, *totrim=NULL;
248
+ int len;
249
+ int i;
250
+
251
+ LWDEBUGF(3, "input: %s", str);
252
+
253
+ ptr = strchr(str, '.');
254
+ if ( ! ptr ) return; /* no dot, no decimal digits */
255
+
256
+ LWDEBUGF(3, "ptr: %s", ptr);
257
+
258
+ len = strlen(ptr);
259
+ for (i=len-1; i; i--)
260
+ {
261
+ if ( ptr[i] != '0' ) break;
262
+ totrim=&ptr[i];
263
+ }
264
+ if ( totrim )
265
+ {
266
+ if ( ptr == totrim-1 ) *ptr = '\0';
267
+ else *totrim = '\0';
268
+ }
269
+
270
+ LWDEBUGF(3, "output: %s", str);
271
+ }
272
+
273
+
274
+ /*
275
+ * Returns a new string which contains a maximum of maxlength characters starting
276
+ * from startpos and finishing at endpos (0-based indexing). If the string is
277
+ * truncated then the first or last characters are replaced by "..." as
278
+ * appropriate.
279
+ *
280
+ * The caller should specify start or end truncation by setting the truncdirection
281
+ * parameter as follows:
282
+ * 0 - start truncation (i.e. characters are removed from the beginning)
283
+ * 1 - end trunctation (i.e. characters are removed from the end)
284
+ */
285
+
286
+ char *lwmessage_truncate(char *str, int startpos, int endpos, int maxlength, int truncdirection)
287
+ {
288
+ char *output;
289
+ char *outstart;
290
+
291
+ /* Allocate space for new string */
292
+ output = lwalloc(maxlength + 4);
293
+ output[0] = '\0';
294
+
295
+ /* Start truncation */
296
+ if (truncdirection == 0)
297
+ {
298
+ /* Calculate the start position */
299
+ if (endpos - startpos < maxlength)
300
+ {
301
+ outstart = str + startpos;
302
+ strncat(output, outstart, endpos - startpos + 1);
303
+ }
304
+ else
305
+ {
306
+ if (maxlength >= 3)
307
+ {
308
+ /* Add "..." prefix */
309
+ outstart = str + endpos + 1 - maxlength + 3;
310
+ strncat(output, "...", 3);
311
+ strncat(output, outstart, maxlength - 3);
312
+ }
313
+ else
314
+ {
315
+ /* maxlength is too small; just output "..." */
316
+ strncat(output, "...", 3);
317
+ }
318
+ }
319
+ }
320
+
321
+ /* End truncation */
322
+ if (truncdirection == 1)
323
+ {
324
+ /* Calculate the end position */
325
+ if (endpos - startpos < maxlength)
326
+ {
327
+ outstart = str + startpos;
328
+ strncat(output, outstart, endpos - startpos + 1);
329
+ }
330
+ else
331
+ {
332
+ if (maxlength >= 3)
333
+ {
334
+ /* Add "..." suffix */
335
+ outstart = str + startpos;
336
+ strncat(output, outstart, maxlength - 3);
337
+ strncat(output, "...", 3);
338
+ }
339
+ else
340
+ {
341
+ /* maxlength is too small; just output "..." */
342
+ strncat(output, "...", 3);
343
+ }
344
+ }
345
+ }
346
+
347
+ return output;
348
+ }
349
+
350
+
351
+ char
352
+ getMachineEndian(void)
353
+ {
354
+ static int endian_check_int = 1; /* dont modify this!!! */
355
+
356
+ return *((char *) &endian_check_int); /* 0 = big endian | xdr,
357
+ * 1 = little endian | ndr
358
+ */
359
+ }
360
+
361
+
362
+ void
363
+ errorIfSRIDMismatch(int srid1, int srid2)
364
+ {
365
+ if ( srid1 != srid2 )
366
+ {
367
+ lwerror("Operation on mixed SRID geometries");
368
+ }
369
+ }
@@ -0,0 +1,861 @@
1
+ /**********************************************************************
2
+ * $Id: measures.c 3639 2009-02-04 00:28:37Z pramsey $
3
+ *
4
+ * PostGIS - Spatial Types for PostgreSQL
5
+ * http://postgis.refractions.net
6
+ * Copyright 2001-2006 Refractions Research Inc.
7
+ *
8
+ * This is free software; you can redistribute and/or modify it under
9
+ * the terms of the GNU General Public Licence. See the COPYING file.
10
+ *
11
+ **********************************************************************/
12
+
13
+ #include <math.h>
14
+ #include <string.h>
15
+
16
+ #include "liblwgeom.h"
17
+
18
+
19
+ /*
20
+ * pt_in_ring_2d(): crossing number test for a point in a polygon
21
+ * input: p = a point,
22
+ * pa = vertex points of a ring V[n+1] with V[n]=V[0]
23
+ * returns: 0 = outside, 1 = inside
24
+ *
25
+ * Our polygons have first and last point the same,
26
+ *
27
+ */
28
+ int pt_in_ring_2d(POINT2D *p, POINTARRAY *ring)
29
+ {
30
+ int cn = 0; /* the crossing number counter */
31
+ int i;
32
+ POINT2D v1, v2;
33
+
34
+ #if INTEGRITY_CHECKS
35
+ POINT2D first, last;
36
+
37
+ getPoint2d_p(ring, 0, &first);
38
+ getPoint2d_p(ring, ring->npoints-1, &last);
39
+ if ( memcmp(&first, &last, sizeof(POINT2D)) )
40
+ {
41
+ lwerror("pt_in_ring_2d: V[n] != V[0] (%g %g != %g %g)",
42
+ first.x, first.y, last.x, last.y);
43
+
44
+ }
45
+ #endif
46
+
47
+ LWDEBUGF(2, "pt_in_ring_2d called with point: %g %g", p->x, p->y);
48
+ /* printPA(ring); */
49
+
50
+ /* loop through all edges of the polygon */
51
+ getPoint2d_p(ring, 0, &v1);
52
+ for (i=0; i<ring->npoints-1; i++)
53
+ {
54
+ double vt;
55
+ getPoint2d_p(ring, i+1, &v2);
56
+
57
+ /* edge from vertex i to vertex i+1 */
58
+ if
59
+ (
60
+ /* an upward crossing */
61
+ ((v1.y <= p->y) && (v2.y > p->y))
62
+ /* a downward crossing */
63
+ || ((v1.y > p->y) && (v2.y <= p->y))
64
+ )
65
+ {
66
+
67
+ vt = (double)(p->y - v1.y) / (v2.y - v1.y);
68
+
69
+ /* P.x <intersect */
70
+ if (p->x < v1.x + vt * (v2.x - v1.x))
71
+ {
72
+ /* a valid crossing of y=p.y right of p.x */
73
+ ++cn;
74
+ }
75
+ }
76
+ v1 = v2;
77
+ }
78
+
79
+ LWDEBUGF(3, "pt_in_ring_2d returning %d", cn&1);
80
+
81
+ return (cn&1); /* 0 if even (out), and 1 if odd (in) */
82
+ }
83
+
84
+ double distance2d_pt_pt(POINT2D *p1, POINT2D *p2)
85
+ {
86
+ double hside = p2->x - p1->x;
87
+ double vside = p2->y - p1->y;
88
+
89
+ return sqrt ( hside*hside + vside*vside );
90
+
91
+ /* the above is more readable
92
+ return sqrt(
93
+ (p2->x-p1->x) * (p2->x-p1->x) + (p2->y-p1->y) * (p2->y-p1->y)
94
+ ); */
95
+ }
96
+
97
+ /*distance2d from p to line A->B */
98
+ double distance2d_pt_seg(POINT2D *p, POINT2D *A, POINT2D *B)
99
+ {
100
+ double r,s;
101
+
102
+ /*if start==end, then use pt distance */
103
+ if ( ( A->x == B->x) && (A->y == B->y) )
104
+ return distance2d_pt_pt(p,A);
105
+
106
+ /*
107
+ * otherwise, we use comp.graphics.algorithms
108
+ * Frequently Asked Questions method
109
+ *
110
+ * (1) AC dot AB
111
+ * r = ---------
112
+ * ||AB||^2
113
+ * r has the following meaning:
114
+ * r=0 P = A
115
+ * r=1 P = B
116
+ * r<0 P is on the backward extension of AB
117
+ * r>1 P is on the forward extension of AB
118
+ * 0<r<1 P is interior to AB
119
+ */
120
+
121
+ r = ( (p->x-A->x) * (B->x-A->x) + (p->y-A->y) * (B->y-A->y) )/( (B->x-A->x)*(B->x-A->x) +(B->y-A->y)*(B->y-A->y) );
122
+
123
+ if (r<0) return distance2d_pt_pt(p,A);
124
+ if (r>1) return distance2d_pt_pt(p,B);
125
+
126
+
127
+ /*
128
+ * (2)
129
+ * (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay)
130
+ * s = -----------------------------
131
+ * L^2
132
+ *
133
+ * Then the distance from C to P = |s|*L.
134
+ *
135
+ */
136
+
137
+ s = ( (A->y-p->y)*(B->x-A->x)- (A->x-p->x)*(B->y-A->y) ) /
138
+ ( (B->x-A->x)*(B->x-A->x) +(B->y-A->y)*(B->y-A->y) );
139
+
140
+ return LW_ABS(s) * sqrt(
141
+ (B->x-A->x)*(B->x-A->x) + (B->y-A->y)*(B->y-A->y)
142
+ );
143
+ }
144
+
145
+ /* find the minimum 2d distance from AB to CD */
146
+ double distance2d_seg_seg(POINT2D *A, POINT2D *B, POINT2D *C, POINT2D *D)
147
+ {
148
+
149
+ double s_top, s_bot,s;
150
+ double r_top, r_bot,r;
151
+
152
+ LWDEBUGF(2, "distance2d_seg_seg [%g,%g]->[%g,%g] by [%g,%g]->[%g,%g]",
153
+ A->x,A->y,B->x,B->y, C->x,C->y, D->x, D->y);
154
+
155
+
156
+ /*A and B are the same point */
157
+ if ( ( A->x == B->x) && (A->y == B->y) )
158
+ return distance2d_pt_seg(A,C,D);
159
+
160
+ /*U and V are the same point */
161
+
162
+ if ( ( C->x == D->x) && (C->y == D->y) )
163
+ return distance2d_pt_seg(D,A,B);
164
+
165
+ /* AB and CD are line segments */
166
+ /* from comp.graphics.algo
167
+
168
+ Solving the above for r and s yields
169
+ (Ay-Cy)(Dx-Cx)-(Ax-Cx)(Dy-Cy)
170
+ r = ----------------------------- (eqn 1)
171
+ (Bx-Ax)(Dy-Cy)-(By-Ay)(Dx-Cx)
172
+
173
+ (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay)
174
+ s = ----------------------------- (eqn 2)
175
+ (Bx-Ax)(Dy-Cy)-(By-Ay)(Dx-Cx)
176
+ Let P be the position vector of the intersection point, then
177
+ P=A+r(B-A) or
178
+ Px=Ax+r(Bx-Ax)
179
+ Py=Ay+r(By-Ay)
180
+ By examining the values of r & s, you can also determine some other limiting conditions:
181
+ If 0<=r<=1 & 0<=s<=1, intersection exists
182
+ r<0 or r>1 or s<0 or s>1 line segments do not intersect
183
+ If the denominator in eqn 1 is zero, AB & CD are parallel
184
+ If the numerator in eqn 1 is also zero, AB & CD are collinear.
185
+
186
+ */
187
+ r_top = (A->y-C->y)*(D->x-C->x) - (A->x-C->x)*(D->y-C->y) ;
188
+ r_bot = (B->x-A->x)*(D->y-C->y) - (B->y-A->y)*(D->x-C->x) ;
189
+
190
+ s_top = (A->y-C->y)*(B->x-A->x) - (A->x-C->x)*(B->y-A->y);
191
+ s_bot = (B->x-A->x)*(D->y-C->y) - (B->y-A->y)*(D->x-C->x);
192
+
193
+ if ( (r_bot==0) || (s_bot == 0) )
194
+ {
195
+ return (
196
+ LW_MIN(distance2d_pt_seg(A,C,D),
197
+ LW_MIN(distance2d_pt_seg(B,C,D),
198
+ LW_MIN(distance2d_pt_seg(C,A,B),
199
+ distance2d_pt_seg(D,A,B))
200
+ )
201
+ )
202
+ );
203
+ }
204
+ s = s_top/s_bot;
205
+ r= r_top/r_bot;
206
+
207
+ if ((r<0) || (r>1) || (s<0) || (s>1) )
208
+ {
209
+ /*no intersection */
210
+ return (
211
+ LW_MIN(distance2d_pt_seg(A,C,D),
212
+ LW_MIN(distance2d_pt_seg(B,C,D),
213
+ LW_MIN(distance2d_pt_seg(C,A,B),
214
+ distance2d_pt_seg(D,A,B))
215
+ )
216
+ )
217
+ );
218
+
219
+ }
220
+ else
221
+ return -0; /*intersection exists */
222
+
223
+ }
224
+
225
+ /*
226
+ * search all the segments of pointarray to see which one is closest to p1
227
+ * Returns minimum distance between point and pointarray
228
+ */
229
+ double distance2d_pt_ptarray(POINT2D *p, POINTARRAY *pa)
230
+ {
231
+ double result = 0;
232
+ int t;
233
+ POINT2D start, end;
234
+
235
+ getPoint2d_p(pa, 0, &start);
236
+
237
+ for (t=1; t<pa->npoints; t++)
238
+ {
239
+ double dist;
240
+ getPoint2d_p(pa, t, &end);
241
+ dist = distance2d_pt_seg(p, &start, &end);
242
+ if (t==1) result = dist;
243
+ else result = LW_MIN(result, dist);
244
+
245
+ if ( result == 0 ) return 0;
246
+
247
+ start = end;
248
+ }
249
+
250
+ return result;
251
+ }
252
+
253
+ /* test each segment of l1 against each segment of l2. Return min */
254
+ double distance2d_ptarray_ptarray(POINTARRAY *l1, POINTARRAY *l2)
255
+ {
256
+ double result = 99999999999.9;
257
+ char result_okay = 0; /*result is a valid min */
258
+ int t,u;
259
+ POINT2D start, end;
260
+ POINT2D start2, end2;
261
+
262
+ LWDEBUGF(2, "distance2d_ptarray_ptarray called (points: %d-%d)",
263
+ l1->npoints, l2->npoints);
264
+
265
+ getPoint2d_p(l1, 0, &start);
266
+ for (t=1; t<l1->npoints; t++) /*for each segment in L1 */
267
+ {
268
+ getPoint2d_p(l1, t, &end);
269
+
270
+ getPoint2d_p(l2, 0, &start2);
271
+ for (u=1; u<l2->npoints; u++) /*for each segment in L2 */
272
+ {
273
+ double dist;
274
+
275
+ getPoint2d_p(l2, u, &end2);
276
+
277
+ dist = distance2d_seg_seg(&start, &end, &start2, &end2);
278
+
279
+ LWDEBUGF(4, "line_line; seg %i * seg %i, dist = %g\n",t,u,dist);
280
+
281
+ if (result_okay)
282
+ result = LW_MIN(result,dist);
283
+ else
284
+ {
285
+ result_okay = 1;
286
+ result = dist;
287
+ }
288
+
289
+ LWDEBUGF(3, " seg%d-seg%d dist: %f, mindist: %f",
290
+ t, u, dist, result);
291
+
292
+ if (result <= 0) return 0; /*intersection */
293
+
294
+ start2 = end2;
295
+ }
296
+ start = end;
297
+ }
298
+
299
+ return result;
300
+ }
301
+
302
+ /* true if point is in poly (and not in its holes) */
303
+ int pt_in_poly_2d(POINT2D *p, LWPOLY *poly)
304
+ {
305
+ int i;
306
+
307
+ /* Not in outer ring */
308
+ if ( ! pt_in_ring_2d(p, poly->rings[0]) ) return 0;
309
+
310
+ /* Check holes */
311
+ for (i=1; i<poly->nrings; i++)
312
+ {
313
+ /* Inside a hole */
314
+ if ( pt_in_ring_2d(p, poly->rings[i]) ) return 0;
315
+ }
316
+
317
+ return 1; /* In outer ring, not in holes */
318
+ }
319
+
320
+ /*
321
+ * Brute force.
322
+ * Test line-ring distance against each ring.
323
+ * If there's an intersection (distance==0) then return 0 (crosses boundary).
324
+ * Otherwise, test to see if any point is inside outer rings of polygon,
325
+ * but not in inner rings.
326
+ * If so, return 0 (line inside polygon),
327
+ * otherwise return min distance to a ring (could be outside
328
+ * polygon or inside a hole)
329
+ */
330
+ double distance2d_ptarray_poly(POINTARRAY *pa, LWPOLY *poly)
331
+ {
332
+ POINT2D pt;
333
+ int i;
334
+ double mindist = 0;
335
+
336
+ LWDEBUGF(2, "distance2d_ptarray_poly called (%d rings)", poly->nrings);
337
+
338
+ for (i=0; i<poly->nrings; i++)
339
+ {
340
+ double dist = distance2d_ptarray_ptarray(pa, poly->rings[i]);
341
+ if (i) mindist = LW_MIN(mindist, dist);
342
+ else mindist = dist;
343
+
344
+ LWDEBUGF(3, " distance from ring %d: %f, mindist: %f",
345
+ i, dist, mindist);
346
+
347
+ if ( mindist <= 0 ) return 0.0; /* intersection */
348
+ }
349
+
350
+ /*
351
+ * No intersection, have to check if a point is
352
+ * inside polygon
353
+ */
354
+ getPoint2d_p(pa, 0, &pt);
355
+
356
+ /*
357
+ * Outside outer ring, so min distance to a ring
358
+ * is the actual min distance
359
+ */
360
+ if ( ! pt_in_ring_2d(&pt, poly->rings[0]) ) return mindist;
361
+
362
+
363
+ /*
364
+ * Its in the outer ring.
365
+ * Have to check if its inside a hole
366
+ */
367
+ for (i=1; i<poly->nrings; i++)
368
+ {
369
+ if ( pt_in_ring_2d(&pt, poly->rings[i]) )
370
+ {
371
+ /*
372
+ * Its inside a hole, then the actual
373
+ * distance is the min ring distance
374
+ */
375
+ return mindist;
376
+ }
377
+ }
378
+
379
+ return 0.0; /* Not in hole, so inside polygon */
380
+ }
381
+
382
+ double distance2d_point_point(LWPOINT *point1, LWPOINT *point2)
383
+ {
384
+ POINT2D p1;
385
+ POINT2D p2;
386
+
387
+ getPoint2d_p(point1->point, 0, &p1);
388
+ getPoint2d_p(point2->point, 0, &p2);
389
+
390
+ return distance2d_pt_pt(&p1, &p2);
391
+ }
392
+
393
+ double distance2d_point_line(LWPOINT *point, LWLINE *line)
394
+ {
395
+ POINT2D p;
396
+ POINTARRAY *pa = line->points;
397
+ getPoint2d_p(point->point, 0, &p);
398
+ return distance2d_pt_ptarray(&p, pa);
399
+ }
400
+
401
+ double distance2d_line_line(LWLINE *line1, LWLINE *line2)
402
+ {
403
+ POINTARRAY *pa1 = line1->points;
404
+ POINTARRAY *pa2 = line2->points;
405
+ return distance2d_ptarray_ptarray(pa1, pa2);
406
+ }
407
+
408
+ /*
409
+ * 1. see if pt in outer boundary. if no, then treat the outer ring like a line
410
+ * 2. if in the boundary, test to see if its in a hole.
411
+ * if so, then return dist to hole, else return 0 (point in polygon)
412
+ */
413
+ double distance2d_point_poly(LWPOINT *point, LWPOLY *poly)
414
+ {
415
+ POINT2D p;
416
+ int i;
417
+
418
+ getPoint2d_p(point->point, 0, &p);
419
+
420
+ LWDEBUG(2, "distance2d_point_poly called");
421
+
422
+ /* Return distance to outer ring if not inside it */
423
+ if ( ! pt_in_ring_2d(&p, poly->rings[0]) )
424
+ {
425
+ LWDEBUG(3, " not inside outer-ring");
426
+
427
+ return distance2d_pt_ptarray(&p, poly->rings[0]);
428
+ }
429
+
430
+ /*
431
+ * Inside the outer ring.
432
+ * Scan though each of the inner rings looking to
433
+ * see if its inside. If not, distance==0.
434
+ * Otherwise, distance = pt to ring distance
435
+ */
436
+ for (i=1; i<poly->nrings; i++)
437
+ {
438
+ /* Inside a hole. Distance = pt -> ring */
439
+ if ( pt_in_ring_2d(&p, poly->rings[i]) )
440
+ {
441
+ LWDEBUG(3, " inside an hole");
442
+
443
+ return distance2d_pt_ptarray(&p, poly->rings[i]);
444
+ }
445
+ }
446
+
447
+ LWDEBUG(3, " inside the polygon");
448
+
449
+ return 0.0; /* Is inside the polygon */
450
+ }
451
+
452
+ /*
453
+ * Brute force.
454
+ * Test to see if any rings intersect.
455
+ * If yes, dist=0.
456
+ * Test to see if one inside the other and if they are inside holes.
457
+ * Find min distance ring-to-ring.
458
+ */
459
+ double distance2d_poly_poly(LWPOLY *poly1, LWPOLY *poly2)
460
+ {
461
+ POINT2D pt;
462
+ double mindist = -1;
463
+ int i;
464
+
465
+ LWDEBUG(2, "distance2d_poly_poly called");
466
+
467
+ /* if poly1 inside poly2 return 0 */
468
+ getPoint2d_p(poly1->rings[0], 0, &pt);
469
+ if ( pt_in_poly_2d(&pt, poly2) ) return 0.0;
470
+
471
+ /* if poly2 inside poly1 return 0 */
472
+ getPoint2d_p(poly2->rings[0], 0, &pt);
473
+ if ( pt_in_poly_2d(&pt, poly1) ) return 0.0;
474
+
475
+ LWDEBUG(3, " polys not inside each other");
476
+
477
+ /*
478
+ * foreach ring in Poly1
479
+ * foreach ring in Poly2
480
+ * if intersect, return 0
481
+ */
482
+ for (i=0; i<poly1->nrings; i++)
483
+ {
484
+ int j;
485
+ for (j=0; j<poly2->nrings; j++)
486
+ {
487
+ double d = distance2d_ptarray_ptarray(poly1->rings[i],
488
+ poly2->rings[j]);
489
+ if ( d <= 0 ) return 0.0;
490
+
491
+ /* mindist is -1 when not yet set */
492
+ if (mindist > -1) mindist = LW_MIN(mindist, d);
493
+ else mindist = d;
494
+
495
+ LWDEBUGF(3, " ring%i-%i dist: %f, mindist: %f", i, j, d, mindist);
496
+ }
497
+
498
+ }
499
+
500
+ /* otherwise return closest approach of rings (no intersection) */
501
+ return mindist;
502
+
503
+ }
504
+
505
+ double distance2d_line_poly(LWLINE *line, LWPOLY *poly)
506
+ {
507
+ return distance2d_ptarray_poly(line->points, poly);
508
+ }
509
+
510
+
511
+ /*find the 2d length of the given POINTARRAY (even if it's 3d) */
512
+ double lwgeom_pointarray_length2d(POINTARRAY *pts)
513
+ {
514
+ double dist = 0.0;
515
+ int i;
516
+ POINT2D frm;
517
+ POINT2D to;
518
+
519
+ if ( pts->npoints < 2 ) return 0.0;
520
+ for (i=0; i<pts->npoints-1;i++)
521
+ {
522
+ getPoint2d_p(pts, i, &frm);
523
+ getPoint2d_p(pts, i+1, &to);
524
+ dist += sqrt( ( (frm.x - to.x)*(frm.x - to.x) ) +
525
+ ((frm.y - to.y)*(frm.y - to.y) ) );
526
+ }
527
+ return dist;
528
+ }
529
+
530
+ /*
531
+ * Find the 3d/2d length of the given POINTARRAY
532
+ * (depending on its dimensions)
533
+ */
534
+ double
535
+ lwgeom_pointarray_length(POINTARRAY *pts)
536
+ {
537
+ double dist = 0.0;
538
+ int i;
539
+ POINT3DZ frm;
540
+ POINT3DZ to;
541
+
542
+ if ( pts->npoints < 2 ) return 0.0;
543
+
544
+ /* compute 2d length if 3d is not available */
545
+ if ( ! TYPE_HASZ(pts->dims) ) return lwgeom_pointarray_length2d(pts);
546
+
547
+ for (i=0; i<pts->npoints-1;i++)
548
+ {
549
+ getPoint3dz_p(pts, i, &frm);
550
+ getPoint3dz_p(pts, i+1, &to);
551
+ dist += sqrt( ( (frm.x - to.x)*(frm.x - to.x) ) +
552
+ ((frm.y - to.y)*(frm.y - to.y) ) +
553
+ ((frm.z - to.z)*(frm.z - to.z) ) );
554
+ }
555
+
556
+ return dist;
557
+ }
558
+
559
+ /*
560
+ * This should be rewritten to make use of the curve itself.
561
+ */
562
+ double
563
+ lwgeom_curvepolygon_area(LWCURVEPOLY *curvepoly)
564
+ {
565
+ LWPOLY *poly = (LWPOLY *)lwgeom_segmentize((LWGEOM *)curvepoly, 32);
566
+ return lwgeom_polygon_area(poly);
567
+ }
568
+
569
+ /*
570
+ * Find the area of the outer ring - sum (area of inner rings).
571
+ * Could use a more numerically stable calculator...
572
+ */
573
+ double
574
+ lwgeom_polygon_area(LWPOLY *poly)
575
+ {
576
+ double poly_area=0.0;
577
+ int i;
578
+ POINT2D p1;
579
+ POINT2D p2;
580
+
581
+ LWDEBUGF(2, "in lwgeom_polygon_area (%d rings)", poly->nrings);
582
+
583
+ for (i=0; i<poly->nrings; i++)
584
+ {
585
+ int j;
586
+ POINTARRAY *ring = poly->rings[i];
587
+ double ringarea = 0.0;
588
+
589
+ LWDEBUGF(4, " rings %d has %d points", i, ring->npoints);
590
+
591
+ for (j=0; j<ring->npoints-1; j++)
592
+ {
593
+ getPoint2d_p(ring, j, &p1);
594
+ getPoint2d_p(ring, j+1, &p2);
595
+ ringarea += ( p1.x * p2.y ) - ( p1.y * p2.x );
596
+ }
597
+
598
+ ringarea /= 2.0;
599
+
600
+ LWDEBUGF(4, " ring 1 has area %lf",ringarea);
601
+
602
+ ringarea = fabs(ringarea);
603
+ if (i != 0) /*outer */
604
+ ringarea = -1.0*ringarea ; /* its a hole */
605
+
606
+ poly_area += ringarea;
607
+ }
608
+
609
+ return poly_area;
610
+ }
611
+
612
+ /*
613
+ * Compute the sum of polygon rings length.
614
+ * Could use a more numerically stable calculator...
615
+ */
616
+ double lwgeom_polygon_perimeter(LWPOLY *poly)
617
+ {
618
+ double result=0.0;
619
+ int i;
620
+
621
+ LWDEBUGF(2, "in lwgeom_polygon_perimeter (%d rings)", poly->nrings);
622
+
623
+ for (i=0; i<poly->nrings; i++)
624
+ result += lwgeom_pointarray_length(poly->rings[i]);
625
+
626
+ return result;
627
+ }
628
+
629
+ /*
630
+ * Compute the sum of polygon rings length (forcing 2d computation).
631
+ * Could use a more numerically stable calculator...
632
+ */
633
+ double lwgeom_polygon_perimeter2d(LWPOLY *poly)
634
+ {
635
+ double result=0.0;
636
+ int i;
637
+
638
+ LWDEBUGF(2, "in lwgeom_polygon_perimeter (%d rings)", poly->nrings);
639
+
640
+ for (i=0; i<poly->nrings; i++)
641
+ result += lwgeom_pointarray_length2d(poly->rings[i]);
642
+
643
+ return result;
644
+ }
645
+
646
+ double
647
+ lwgeom_mindistance2d_recursive(uchar *lw1, uchar *lw2)
648
+ {
649
+ return lwgeom_mindistance2d_recursive_tolerance( lw1, lw2, 0.0 );
650
+ }
651
+
652
+ double
653
+ lwgeom_mindistance2d_recursive_tolerance(uchar *lw1, uchar *lw2, double tolerance)
654
+ {
655
+ LWGEOM_INSPECTED *in1, *in2;
656
+ int i, j;
657
+ double mindist = -1;
658
+
659
+ in1 = lwgeom_inspect(lw1);
660
+ in2 = lwgeom_inspect(lw2);
661
+
662
+ for (i=0; i<in1->ngeometries; i++)
663
+ {
664
+ uchar *g1 = lwgeom_getsubgeometry_inspected(in1, i);
665
+ int t1 = lwgeom_getType(g1[0]);
666
+ double dist=tolerance;
667
+
668
+ /* it's a multitype... recurse */
669
+ if ( lwgeom_contains_subgeoms(t1) )
670
+ {
671
+ dist = lwgeom_mindistance2d_recursive_tolerance(g1, lw2, tolerance);
672
+ if ( dist <= tolerance ) return tolerance; /* can't be closer */
673
+ if ( mindist == -1 ) mindist = dist;
674
+ else mindist = LW_MIN(dist, mindist);
675
+ continue;
676
+ }
677
+
678
+ for (j=0; j<in2->ngeometries; j++)
679
+ {
680
+ uchar *g2 = lwgeom_getsubgeometry_inspected(in2, j);
681
+ int t2 = lwgeom_getType(g2[0]);
682
+
683
+ if ( t1 == POINTTYPE )
684
+ {
685
+ if ( t2 == POINTTYPE )
686
+ {
687
+ dist = distance2d_point_point(
688
+ lwpoint_deserialize(g1),
689
+ lwpoint_deserialize(g2)
690
+ );
691
+ }
692
+ else if ( t2 == LINETYPE )
693
+ {
694
+ dist = distance2d_point_line(
695
+ lwpoint_deserialize(g1),
696
+ lwline_deserialize(g2)
697
+ );
698
+ }
699
+ else if ( t2 == POLYGONTYPE )
700
+ {
701
+ dist = distance2d_point_poly(
702
+ lwpoint_deserialize(g1),
703
+ lwpoly_deserialize(g2)
704
+ );
705
+ }
706
+ else
707
+ {
708
+ lwerror("Unsupported geometry type: %s", lwgeom_typename(t2));
709
+ }
710
+ }
711
+ else if ( t1 == LINETYPE )
712
+ {
713
+ if ( t2 == POINTTYPE )
714
+ {
715
+ dist = distance2d_point_line(
716
+ lwpoint_deserialize(g2),
717
+ lwline_deserialize(g1)
718
+ );
719
+ }
720
+ else if ( t2 == LINETYPE )
721
+ {
722
+ dist = distance2d_line_line(
723
+ lwline_deserialize(g1),
724
+ lwline_deserialize(g2)
725
+ );
726
+ }
727
+ else if ( t2 == POLYGONTYPE )
728
+ {
729
+ dist = distance2d_line_poly(
730
+ lwline_deserialize(g1),
731
+ lwpoly_deserialize(g2)
732
+ );
733
+ }
734
+ else
735
+ {
736
+ lwerror("Unsupported geometry type: %s", lwgeom_typename(t2));
737
+ }
738
+ }
739
+ else if ( t1 == POLYGONTYPE )
740
+ {
741
+ if ( t2 == POLYGONTYPE )
742
+ {
743
+ dist = distance2d_poly_poly(
744
+ lwpoly_deserialize(g2),
745
+ lwpoly_deserialize(g1)
746
+ );
747
+ }
748
+ else if ( t2 == POINTTYPE )
749
+ {
750
+ dist = distance2d_point_poly(
751
+ lwpoint_deserialize(g2),
752
+ lwpoly_deserialize(g1)
753
+ );
754
+ }
755
+ else if ( t2 == LINETYPE )
756
+ {
757
+ dist = distance2d_line_poly(
758
+ lwline_deserialize(g2),
759
+ lwpoly_deserialize(g1)
760
+ );
761
+ }
762
+ else
763
+ {
764
+ lwerror("Unsupported geometry type: %s", lwgeom_typename(t2));
765
+ }
766
+ }
767
+ else if (lwgeom_contains_subgeoms(t1)) /* it's a multitype... recurse */
768
+ {
769
+ dist = lwgeom_mindistance2d_recursive_tolerance(g1, g2, tolerance);
770
+ }
771
+ else
772
+ {
773
+ lwerror("Unsupported geometry type: %s", lwgeom_typename(t1));
774
+ }
775
+
776
+ if (mindist == -1 ) mindist = dist;
777
+ else mindist = LW_MIN(dist, mindist);
778
+
779
+ LWDEBUGF(3, "dist %d-%d: %f - mindist: %f",
780
+ i, j, dist, mindist);
781
+
782
+
783
+ if (mindist <= tolerance) return tolerance; /* can't be closer */
784
+
785
+ }
786
+
787
+ }
788
+
789
+ if (mindist<0) mindist = 0;
790
+
791
+ return mindist;
792
+ }
793
+
794
+
795
+
796
+ int
797
+ lwgeom_pt_inside_circle(POINT2D *p, double cx, double cy, double rad)
798
+ {
799
+ POINT2D center;
800
+
801
+ center.x = cx;
802
+ center.y = cy;
803
+
804
+ if ( distance2d_pt_pt(p, &center) < rad ) return 1;
805
+ else return 0;
806
+
807
+ }
808
+
809
+ /*
810
+ * Compute the azimuth of segment AB in radians.
811
+ * Return 0 on exception (same point), 1 otherwise.
812
+ */
813
+ int
814
+ azimuth_pt_pt(POINT2D *A, POINT2D *B, double *d)
815
+ {
816
+ if ( A->x == B->x )
817
+ {
818
+ if ( A->y < B->y ) *d=0.0;
819
+ else if ( A->y > B->y ) *d=M_PI;
820
+ else return 0;
821
+ return 1;
822
+ }
823
+
824
+ if ( A->y == B->y )
825
+ {
826
+ if ( A->x < B->x ) *d=M_PI/2;
827
+ else if ( A->x > B->x ) *d=M_PI+(M_PI/2);
828
+ else return 0;
829
+ return 1;
830
+ }
831
+
832
+ if ( A->x < B->x )
833
+ {
834
+ if ( A->y < B->y )
835
+ {
836
+ *d=atan(fabs(A->x - B->x) / fabs(A->y - B->y) );
837
+ }
838
+ else /* ( A->y > B->y ) - equality case handled above */
839
+ {
840
+ *d=atan(fabs(A->y - B->y) / fabs(A->x - B->x) )
841
+ + (M_PI/2);
842
+ }
843
+ }
844
+
845
+ else /* ( A->x > B->x ) - equality case handled above */
846
+ {
847
+ if ( A->y > B->y )
848
+ {
849
+ *d=atan(fabs(A->x - B->x) / fabs(A->y - B->y) )
850
+ + M_PI;
851
+ }
852
+ else /* ( A->y < B->y ) - equality case handled above */
853
+ {
854
+ *d=atan(fabs(A->y - B->y) / fabs(A->x - B->x) )
855
+ + (M_PI+(M_PI/2));
856
+ }
857
+ }
858
+
859
+ return 1;
860
+ }
861
+