PostGIS 3.7.0dev-r@@SVN_REVISION@@
Loading...
Searching...
No Matches
lwgeom_out_marc21.c
Go to the documentation of this file.
1/**********************************************************************
2 *
3 * PostGIS - Spatial Types for PostgreSQL
4 * http://postgis.net
5 *
6 * PostGIS is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * PostGIS is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with PostGIS. If not, see <http://www.gnu.org/licenses/>.
18 **********************************************************************/
19
20#include "postgres.h"
21#include "fmgr.h"
22#include "utils/builtins.h"
23
24#include "../postgis_config.h"
25#include "liblwgeom.h"
26#include "lwgeom_transform.h"
27
28#include "stringbuffer.h"
29#include "liblwgeom_internal.h"
30
31#define MARC21_NS ((char *) "http://www.loc.gov/MARC21/slim")
32/**********************************************************************
33 * Ability to serialise MARC21/XML Records from geometries. Coordinates
34 * are returned in decimal degrees.
35 *
36 * MARC21/XML version supported: 1.1
37 * MARC21/XML Cartographic Mathematical Data Definition:
38 * https://www.loc.gov/marc/bibliographic/bd034.html
39 *
40 * Copyright (C) 2021 University of Münster (WWU), Germany
41 * Written by Jim Jones <jim.jones@uni-muenster.de>
42 *
43 **********************************************************************/
44
45
46static int gbox_to_marc21_sb(const GBOX box, const char* format, stringbuffer_t *sb);
47static int is_format_valid(const char* format);
48lwvarlena_t* lwgeom_to_marc21(const LWGEOM *geom, const char* format);
49
51Datum ST_AsMARC21(PG_FUNCTION_ARGS) {
52 lwvarlena_t *marc21;
53 int32_t srid;
54 LWPROJ *lwproj;
55 LWGEOM *lwgeom;
56 //uint8_t is_latlong;
57 GSERIALIZED *gs = PG_GETARG_GSERIALIZED_P(0);
58 text *format_text_input = PG_GETARG_TEXT_P(1);
59 const char *format = text_to_cstring(format_text_input);
60
61 srid = gserialized_get_srid(gs);
62
63 if (srid == SRID_UNKNOWN) {
64
65 PG_FREE_IF_COPY(gs, 0);
66 lwpgerror("ST_AsMARC21: Input geometry has unknown (%d) SRID", srid);
67 PG_RETURN_NULL();
68
69 }
70
71 if (lwproj_lookup(srid, srid, &lwproj) == LW_FAILURE) {
72
73 PG_FREE_IF_COPY(gs, 0);
74 lwpgerror("ST_AsMARC21: Failure reading projections from spatial_ref_sys.");
75 PG_RETURN_NULL();
76
77 }
78
79 if (!lwproj_is_latlong(lwproj)) {
80
81 PG_FREE_IF_COPY(gs, 0);
82 lwpgerror("ST_AsMARC21: Unsupported SRID (%d). Only lon/lat coordinate systems are supported in MARC21/XML Documents.", srid);
83 PG_RETURN_NULL();
84 }
85
86
87 lwgeom = lwgeom_from_gserialized(gs);
88 marc21 = lwgeom_to_marc21(lwgeom, format);
89
90 if (marc21) PG_RETURN_TEXT_P(marc21);
91
92 PG_RETURN_NULL();
93}
94
96lwgeom_to_marc21(const LWGEOM *geom, const char* format) {
97
99 GBOX box;
100 lwvarlena_t *v;
101
102 POSTGIS_DEBUGF(2, "lwgeom_to_marc21 called: %s with format %s ", lwtype_name(lwgeom_get_type(geom)), format);
103
104 if (lwgeom_is_empty(geom)) return NULL;
105 if (is_format_valid(format)==LW_FALSE) lwerror("invalid output format: \"%s\"", format);
106
107 POSTGIS_DEBUG(3, "creating stringbuffer");
108 sb = stringbuffer_create();
109
110 POSTGIS_DEBUGF(3, "opening MARC21/XML record: %s", lwtype_name(lwgeom_get_type(geom)));
111
112 if (stringbuffer_aprintf(sb, "<record xmlns=\"%s\">", MARC21_NS) < 0) {
113
115 return NULL;
116
117 }
118
119
120 if (lwgeom_is_collection(geom)) {
121
122 LWCOLLECTION *coll = (LWCOLLECTION*) geom;
123
124 POSTGIS_DEBUGF(3, " collection detected: %s", lwtype_name(lwgeom_get_type(geom)));
125
126 for (uint32_t i = 0; i < coll->ngeoms; i++) {
127
128 if (lwgeom_calculate_gbox(coll->geoms[i], &box) == LW_FAILURE) {
129
131 lwpgerror("failed to calculate bbox for a geometry in the collection: %s", lwtype_name(lwgeom_get_type(coll->geoms[i])));
132
133 }
134
135 if (gbox_to_marc21_sb(box, format, sb) == LW_FAILURE) {
136
138 lwpgerror("failed to create MARC21/XML for a geometry in the collection: %s", lwtype_name(lwgeom_get_type(coll->geoms[i])));
139
140 }
141
142 }
143
144 } else {
145
146 POSTGIS_DEBUGF(3, " calculating gbox: %s", lwtype_name(lwgeom_get_type(geom)));
147
148 if (lwgeom_calculate_gbox(geom, &box) == LW_FAILURE) {
149
151 lwpgerror("failed to calculate bbox for %s", lwtype_name(lwgeom_get_type(geom)));
152
153 }
154
155 POSTGIS_DEBUGF(3, " creating MARC21/XML datafield: %s", lwtype_name(lwgeom_get_type(geom)));
156
157 if (gbox_to_marc21_sb(box, format, sb) == LW_FAILURE) {
158
160 lwpgerror("failed to create MARC21/XML for %s", lwtype_name(lwgeom_get_type(geom)));
161
162 }
163
164 }
165
166 POSTGIS_DEBUG(3, " closing MARC21/XML record");
167
168 if (stringbuffer_aprintf(sb, "</record>") < 0) {
169
171 return LW_FAILURE;
172
173 }
174
177
178 return v;
179}
180
181
182static int is_format_valid(const char* format){
183
184 const char *dec_part = strchr(format, '.');
185 if(!dec_part) dec_part = strchr(format, ',');
186
187 if(!dec_part) {
188
189 if (strcmp(format, "hdddmmss") && strcmp(format, "dddmmss")) {
190
191 return LW_FALSE;
192
193 }
194
195 } else {
196 char* int_part;
197 const size_t dec_part_len = strlen(dec_part);
198 const size_t int_part_len = (size_t)(dec_part - format);
199 if(int_part_len == 0 || dec_part_len<2) return LW_FALSE;
200
201 int_part = palloc(int_part_len + 1);
202 memcpy(int_part, &format[0], int_part_len);
203 int_part[int_part_len]='\0';
204
205 if (strcmp(int_part,"hddd") && strcmp(int_part,"ddd") &&
206 strcmp(int_part,"hdddmm") && strcmp(int_part,"dddmm") &&
207 strcmp(int_part,"hdddmmss") && strcmp(int_part,"dddmmss")) {
208
209 pfree(int_part);
210 return LW_FALSE;
211
212 }
213
214 for (size_t i = 1; i < dec_part_len; i++) {
215
216 if(dec_part[i]!=int_part[int_part_len-1]) {
217
218 pfree(int_part);
219 return LW_FALSE;
220 }
221 }
222
223 pfree(int_part);
224
225 }
226
227 return LW_TRUE;
228
229}
230
231static int corner_to_subfield_sb(stringbuffer_t *sb, double decimal_degrees, const char*format, char subfield) {
232
233 char cardinal_direction;
234 char decimal_separator;
235
236 int degrees = (int) decimal_degrees;
237 double minutes = fabs((decimal_degrees-degrees)*60);
238 double seconds = fabs((minutes-(int)minutes) *60);
239
240 int has_cardinal_direction = 0;
241 int num_decimals = 0;
242 char* res = palloc(sizeof(char)*strlen(format)+2);
243
244 /* size of the buffer for the output snprintf calls.
245 * the output strings must have the same length as the format.
246 * +1 to make room for the null character '\0' */
247 size_t buffer_size = strlen(format)+1;
248
249 /* +1 one digit to the buffer size in case of negative
250 * numbers to account for the "-" sign */
251 if(degrees < 0) buffer_size = buffer_size+1;
252
253
254 POSTGIS_DEBUGF(2,"corner_to_subfield_sb called with coordinates: %f and format: %s",decimal_degrees,format);
255
256 if((int)(seconds + 0.5)>=60) {
257
258 seconds = seconds-60;
259 minutes = minutes +1;
260
261 }
262
263
264 if(strchr(format, '.')) {
265 num_decimals = strlen(strchr(format, '.'))-1;
266 decimal_separator = '.';
267 }
268
269 if(strchr(format, ',')) {
270 num_decimals = strlen(strchr(format, ','))-1;
271 decimal_separator = ',';
272 }
273
274 if(format[0]=='h'){
275
276 has_cardinal_direction = 1;
277
278 if(subfield=='d'||subfield=='e'){
279
280 if(decimal_degrees>0){
281
282 cardinal_direction='E';
283
284 } else {
285
286 cardinal_direction='W';
287 degrees=abs(degrees);
288 decimal_degrees= fabs(decimal_degrees);
289
290 }
291 }
292
293 if(subfield=='f'||subfield=='g'){
294
295 if(decimal_degrees>0){
296
297 cardinal_direction='N';
298
299 } else {
300
301 cardinal_direction='S';
302 degrees=abs(degrees);
303 decimal_degrees= fabs(decimal_degrees);
304
305 }
306 }
307
308
309 }
310
311 if(format[3+has_cardinal_direction]=='.' || format[3+has_cardinal_direction]==',' ) {
312
317 int pad_degrees = (int)strlen(format);
318
319 if(decimal_degrees <0 && decimal_degrees>-100) pad_degrees=strlen(format)+1;
320
321 if(has_cardinal_direction) pad_degrees=pad_degrees-1;
322
323 snprintf(res,buffer_size,"%0*.*f",pad_degrees,num_decimals,decimal_degrees);
324
325
326 } else if(format[5+has_cardinal_direction]=='.' || format[5+has_cardinal_direction]==',' ) {
327
331 int pad_minutes = 0;
332
333 if(minutes<10) pad_minutes = (int)strlen(format)-has_cardinal_direction-3;
334
335 snprintf(res,buffer_size,"%.3d%0*.*f",degrees,pad_minutes,num_decimals,fabs(minutes));
336
337 }
338
339 else if(format[7+has_cardinal_direction]=='.' || format[7+has_cardinal_direction]==',') {
340
341 /*
342 * decimal seconds
343 */
344
345 int pad_seconds = 0;
346
347 if(seconds<10) pad_seconds = (int) strlen(format)-has_cardinal_direction-5;
348
349 snprintf(res,buffer_size,"%.3d%.2d%0*.*f",degrees,(int)minutes,pad_seconds,num_decimals,fabs(seconds));
350
351 } else {
352
357 snprintf(res,buffer_size,"%.3d%.2d%.2d",degrees,(int)minutes,(int)(seconds + 0.5));
358
359 }
360
361
362 if(decimal_separator==','){
363
364 res[strlen(res)-num_decimals-1] = ',';
365
366 }
367
368 if(has_cardinal_direction){
369
370 if (stringbuffer_aprintf(sb, "<subfield code=\"%c\">%c%s</subfield>", subfield, cardinal_direction, res) < 0) return LW_FAILURE;
371
372 } else {
373
374 if (stringbuffer_aprintf(sb, "<subfield code=\"%c\">%s</subfield>", subfield, res) < 0) return LW_FAILURE;
375
376 }
377
378
379 pfree(res);
380 return LW_SUCCESS;
381
382
383
384}
385
386static int gbox_to_marc21_sb(const GBOX box, const char* format, stringbuffer_t *sb) {
387
388 POSTGIS_DEBUG(2, "gbox_to_marc21_sb called");
389
390 if (stringbuffer_aprintf(sb, "<datafield tag=\"034\" ind1=\"1\" ind2=\" \">") < 0) return LW_FAILURE;
391 if (stringbuffer_aprintf(sb, "<subfield code=\"a\">a</subfield>") < 0) return LW_FAILURE;
392 if (!corner_to_subfield_sb(sb, box.xmin, format, 'd')) return LW_FAILURE;
393 if (!corner_to_subfield_sb(sb, box.xmax, format, 'e')) return LW_FAILURE;
394 if (!corner_to_subfield_sb(sb, box.ymax, format, 'f')) return LW_FAILURE;
395 if (!corner_to_subfield_sb(sb, box.ymin, format, 'g')) return LW_FAILURE;
396 if (stringbuffer_aprintf(sb, "</datafield>") < 0) return LW_FAILURE;
397
398 POSTGIS_DEBUG(2, "=> gbox_to_marc21_sb returns LW_SUCCESS");
399
400 return LW_SUCCESS;
401}
int32_t gserialized_get_srid(const GSERIALIZED *g)
Extract the SRID from the serialized form (it is packed into three bytes so this is a handy function)...
LWGEOM * lwgeom_from_gserialized(const GSERIALIZED *g)
Allocate a new LWGEOM from a GSERIALIZED.
const char * lwtype_name(uint8_t type)
Return the type name string associated with a type number (e.g.
Definition lwutil.c:216
#define LW_FALSE
Definition liblwgeom.h:94
#define LW_FAILURE
Definition liblwgeom.h:96
#define LW_SUCCESS
Definition liblwgeom.h:97
int lwgeom_is_collection(const LWGEOM *lwgeom)
Determine whether a LWGEOM contains sub-geometries or not This basically just checks that the struct ...
Definition lwgeom.c:1125
int lwgeom_calculate_gbox(const LWGEOM *lwgeom, GBOX *gbox)
Calculate bounding box of a geometry, automatically taking into account whether it is cartesian or ge...
Definition lwgeom.c:783
#define LW_TRUE
Return types for functions with status returns.
Definition liblwgeom.h:93
#define SRID_UNKNOWN
Unknown SRID value.
Definition liblwgeom.h:215
This library is the generic geometry handling section of PostGIS.
void void lwerror(const char *fmt,...) __attribute__((format(printf
Write a notice out to the error handler.
lwvarlena_t * lwgeom_to_marc21(const LWGEOM *geom, const char *format)
static int corner_to_subfield_sb(stringbuffer_t *sb, double decimal_degrees, const char *format, char subfield)
static int gbox_to_marc21_sb(const GBOX box, const char *format, stringbuffer_t *sb)
PG_FUNCTION_INFO_V1(ST_AsMARC21)
static int is_format_valid(const char *format)
#define MARC21_NS
Datum ST_AsMARC21(PG_FUNCTION_ARGS)
static uint32_t lwgeom_get_type(const LWGEOM *geom)
Return LWTYPE number.
Definition lwinline.h:141
static int lwgeom_is_empty(const LWGEOM *geom)
Return true or false depending on whether a geometry is an "empty" geometry (no vertices members)
Definition lwinline.h:199
#define buffer_size
Definition lwout_wkt.c:32
stringbuffer_t * stringbuffer_create(void)
Allocate a new stringbuffer_t.
int stringbuffer_aprintf(stringbuffer_t *s, const char *fmt,...)
Appends a formatted string to the current string buffer, using the format and argument list provided.
lwvarlena_t * stringbuffer_getvarlenacopy(stringbuffer_t *s)
void stringbuffer_destroy(stringbuffer_t *s)
Free the stringbuffer_t and all memory managed within it.
double ymax
Definition liblwgeom.h:357
double xmax
Definition liblwgeom.h:355
double ymin
Definition liblwgeom.h:356
double xmin
Definition liblwgeom.h:354
uint32_t ngeoms
Definition liblwgeom.h:580
LWGEOM ** geoms
Definition liblwgeom.h:575