PostGIS  3.7.0dev-r@@SVN_REVISION@@
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 
46 static int gbox_to_marc21_sb(const GBOX box, const char* format, stringbuffer_t *sb);
47 static int is_format_valid(const char* format);
48 lwvarlena_t* lwgeom_to_marc21(const LWGEOM *geom, const char* format);
49 
51 Datum 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 
96 lwgeom_to_marc21(const LWGEOM *geom, const char* format) {
97 
98  stringbuffer_t *sb;
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 
182 static 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 
231 static 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 
386 static 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)...
Definition: gserialized.c:155
LWGEOM * lwgeom_from_gserialized(const GSERIALIZED *g)
Allocate a new LWGEOM from a GSERIALIZED.
Definition: gserialized.c:268
#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:1097
const char * lwtype_name(uint8_t type)
Return the type name string associated with a type number (e.g.
Definition: lwutil.c:216
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:755
#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
tuple res
Definition: window.py:79
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.
Definition: stringbuffer.c:254
lwvarlena_t * stringbuffer_getvarlenacopy(stringbuffer_t *s)
Definition: stringbuffer.c:154
stringbuffer_t * stringbuffer_create(void)
Allocate a new stringbuffer_t.
Definition: stringbuffer.c:33
void stringbuffer_destroy(stringbuffer_t *s)
Free the stringbuffer_t and all memory managed within it.
Definition: stringbuffer.c:85
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