PostGIS  3.4.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 (GetLWPROJ(srid, srid, &lwproj) == LW_FAILURE) {
80 //
81 // PG_FREE_IF_COPY(gs, 0);
82 // lwpgerror("ST_AsMARC21: Failure reading projections from spatial_ref_sys.");
83 // PG_RETURN_NULL();
84 //
85 // }
86 
87 //#if POSTGIS_PROJ_VERSION < 61
88 // is_latlong = pj_is_latlong(lwproj->pj_from);
89 //#else
90 // is_latlong = lwproj->source_is_latlong;
91 //#endif
92 
93  //is_latlong = lwproj_is_latlong(lwproj);
94 
95  if (!lwproj_is_latlong(lwproj)) {
96 
97  PG_FREE_IF_COPY(gs, 0);
98  lwpgerror("ST_AsMARC21: Unsupported SRID (%d). Only lon/lat coordinate systems are supported in MARC21/XML Documents.", srid);
99  PG_RETURN_NULL();
100  }
101 
102 
103  lwgeom = lwgeom_from_gserialized(gs);
104  marc21 = lwgeom_to_marc21(lwgeom, format);
105 
106  if (marc21) PG_RETURN_TEXT_P(marc21);
107 
108  PG_RETURN_NULL();
109 }
110 
112 lwgeom_to_marc21(const LWGEOM *geom, const char* format) {
113 
114  stringbuffer_t *sb;
115  GBOX box;
116  lwvarlena_t *v;
117 
118  POSTGIS_DEBUGF(2, "lwgeom_to_marc21 called: %s with format %s ", lwtype_name(lwgeom_get_type(geom)), format);
119 
120  if (lwgeom_is_empty(geom)) return NULL;
121  if (is_format_valid(format)==LW_FALSE) lwerror("invalid output format: \"%s\"", format);
122 
123  POSTGIS_DEBUG(3, "creating stringbuffer");
124  sb = stringbuffer_create();
125 
126  POSTGIS_DEBUGF(3, "opening MARC21/XML record: %s", lwtype_name(lwgeom_get_type(geom)));
127 
128  if (stringbuffer_aprintf(sb, "<record xmlns=\"%s\">", MARC21_NS) < 0) {
129 
131  return NULL;
132 
133  }
134 
135 
136  if (lwgeom_is_collection(geom)) {
137 
138  LWCOLLECTION *coll = (LWCOLLECTION*) geom;
139 
140  POSTGIS_DEBUGF(3, " collection detected: %s", lwtype_name(lwgeom_get_type(geom)));
141 
142  for (uint32_t i = 0; i < coll->ngeoms; i++) {
143 
144  if (lwgeom_calculate_gbox(coll->geoms[i], &box) == LW_FAILURE) {
145 
147  lwpgerror("failed to calculate bbox for a geometry in the collection: %s", lwtype_name(lwgeom_get_type(coll->geoms[i])));
148 
149  }
150 
151  if (gbox_to_marc21_sb(box, format, sb) == LW_FAILURE) {
152 
154  lwpgerror("failed to create MARC21/XML for a geometry in the collection: %s", lwtype_name(lwgeom_get_type(coll->geoms[i])));
155 
156  }
157 
158  }
159 
160  } else {
161 
162  POSTGIS_DEBUGF(3, " calculating gbox: %s", lwtype_name(lwgeom_get_type(geom)));
163 
164  if (lwgeom_calculate_gbox(geom, &box) == LW_FAILURE) {
165 
167  lwpgerror("failed to calculate bbox for %s", lwtype_name(lwgeom_get_type(geom)));
168 
169  }
170 
171  POSTGIS_DEBUGF(3, " creating MARC21/XML datafield: %s", lwtype_name(lwgeom_get_type(geom)));
172 
173  if (gbox_to_marc21_sb(box, format, sb) == LW_FAILURE) {
174 
176  lwpgerror("failed to create MARC21/XML for %s", lwtype_name(lwgeom_get_type(geom)));
177 
178  }
179 
180  }
181 
182  POSTGIS_DEBUG(3, " closing MARC21/XML record");
183 
184  if (stringbuffer_aprintf(sb, "</record>") < 0) {
185 
187  return LW_FAILURE;
188 
189  }
190 
193 
194  return v;
195 }
196 
197 
198 static int is_format_valid(const char* format){
199 
200  char *int_part;
201  char *dec_part = strchr(format, '.');
202  if(!dec_part) dec_part = strchr(format, ',');
203 
204  if(!dec_part) {
205 
206  if (strcmp(format, "hdddmmss") && strcmp(format, "dddmmss")) {
207 
208  return LW_FALSE;
209 
210  }
211 
212  } else {
213 
214  if(strlen(dec_part)<2) return LW_FALSE;
215 
216  int_part = palloc(sizeof(char)*strlen(format));
217  memcpy(int_part, &format[0], strlen(format) - strlen(dec_part));
218  int_part[strlen(format) - strlen(dec_part)]='\0';
219 
220  if (strcmp(int_part,"hddd") && strcmp(int_part,"ddd") &&
221  strcmp(int_part,"hdddmm") && strcmp(int_part,"dddmm") &&
222  strcmp(int_part,"hdddmmss") && strcmp(int_part,"dddmmss")) {
223 
224  pfree(int_part);
225  return LW_FALSE;
226 
227  }
228 
229  for (size_t i = 1; i < strlen(dec_part); i++) {
230 
231  if(dec_part[i]!=int_part[strlen(int_part)-1]) {
232 
233  pfree(int_part);
234  return LW_FALSE;
235  }
236  }
237 
238  pfree(int_part);
239 
240  }
241 
242  return LW_TRUE;
243 
244 }
245 
246 static int corner_to_subfield_sb(stringbuffer_t *sb, double decimal_degrees, const char*format, char subfield) {
247 
248  char cardinal_direction;
249  char decimal_separator;
250 
251  int degrees = (int) decimal_degrees;
252  double minutes = fabs((decimal_degrees-degrees)*60);
253  double seconds = fabs((minutes-(int)minutes) *60);
254 
255  int has_cardinal_direction = 0;
256  int num_decimals = 0;
257  char* res = palloc(sizeof(char)*strlen(format)+2);
258 
259  /* size of the buffer for the output snprintf calls.
260  * the output strings must have the same length as the format.
261  * +1 to make room for the null character '\0' */
262  size_t buffer_size = strlen(format)+1;
263 
264  /* +1 one digit to the buffer size in case of negative
265  * numbers to account for the "-" sign */
266  if(degrees < 0) buffer_size = buffer_size+1;
267 
268 
269  POSTGIS_DEBUGF(2,"corner_to_subfield_sb called with coordinates: %f and format: %s",decimal_degrees,format);
270 
271  if((int)(seconds + 0.5)>=60) {
272 
273  seconds = seconds-60;
274  minutes = minutes +1;
275 
276  }
277 
278 
279  if(strchr(format, '.')) {
280  num_decimals = strlen(strchr(format, '.'))-1;
281  decimal_separator = '.';
282  }
283 
284  if(strchr(format, ',')) {
285  num_decimals = strlen(strchr(format, ','))-1;
286  decimal_separator = ',';
287  }
288 
289  if(format[0]=='h'){
290 
291  has_cardinal_direction = 1;
292 
293  if(subfield=='d'||subfield=='e'){
294 
295  if(decimal_degrees>0){
296 
297  cardinal_direction='E';
298 
299  } else {
300 
301  cardinal_direction='W';
302  degrees=abs(degrees);
303  decimal_degrees= fabs(decimal_degrees);
304 
305  }
306  }
307 
308  if(subfield=='f'||subfield=='g'){
309 
310  if(decimal_degrees>0){
311 
312  cardinal_direction='N';
313 
314  } else {
315 
316  cardinal_direction='S';
317  degrees=abs(degrees);
318  decimal_degrees= fabs(decimal_degrees);
319 
320  }
321  }
322 
323 
324  }
325 
326  if(format[3+has_cardinal_direction]=='.' || format[3+has_cardinal_direction]==',' ) {
327 
332  int pad_degrees = (int)strlen(format);
333 
334  if(decimal_degrees <0 && decimal_degrees>-100) pad_degrees=strlen(format)+1;
335 
336  if(has_cardinal_direction) pad_degrees=pad_degrees-1;
337 
338  snprintf(res,buffer_size,"%0*.*f",pad_degrees,num_decimals,decimal_degrees);
339 
340 
341  } else if(format[5+has_cardinal_direction]=='.' || format[5+has_cardinal_direction]==',' ) {
342 
346  int pad_minutes = 0;
347 
348  if(minutes<10) pad_minutes = (int)strlen(format)-has_cardinal_direction-3;
349 
350  snprintf(res,buffer_size,"%.3d%0*.*f",degrees,pad_minutes,num_decimals,fabs(minutes));
351 
352  }
353 
354  else if(format[7+has_cardinal_direction]=='.' || format[7+has_cardinal_direction]==',') {
355 
356  /*
357  * decimal seconds
358  */
359 
360  int pad_seconds = 0;
361 
362  if(seconds<10) pad_seconds = (int) strlen(format)-has_cardinal_direction-5;
363 
364  snprintf(res,buffer_size,"%.3d%.2d%0*.*f",degrees,(int)minutes,pad_seconds,num_decimals,fabs(seconds));
365 
366  } else {
367 
372  snprintf(res,buffer_size,"%.3d%.2d%.2d",degrees,(int)minutes,(int)(seconds + 0.5));
373 
374  }
375 
376 
377  if(decimal_separator==','){
378 
379  res[strlen(res)-num_decimals-1] = ',';
380 
381  }
382 
383  if(has_cardinal_direction){
384 
385  if (stringbuffer_aprintf(sb, "<subfield code=\"%c\">%c%s</subfield>", subfield, cardinal_direction, res) < 0) return LW_FAILURE;
386 
387  } else {
388 
389  if (stringbuffer_aprintf(sb, "<subfield code=\"%c\">%s</subfield>", subfield, res) < 0) return LW_FAILURE;
390 
391  }
392 
393 
394  pfree(res);
395  return LW_SUCCESS;
396 
397 
398 
399 }
400 
401 static int gbox_to_marc21_sb(const GBOX box, const char* format, stringbuffer_t *sb) {
402 
403  POSTGIS_DEBUG(2, "gbox_to_marc21_sb called");
404 
405  if (stringbuffer_aprintf(sb, "<datafield tag=\"034\" ind1=\"1\" ind2=\" \">") < 0) return LW_FAILURE;
406  if (stringbuffer_aprintf(sb, "<subfield code=\"a\">a</subfield>") < 0) return LW_FAILURE;
407  if (!corner_to_subfield_sb(sb, box.xmin, format, 'd')) return LW_FAILURE;
408  if (!corner_to_subfield_sb(sb, box.xmax, format, 'e')) return LW_FAILURE;
409  if (!corner_to_subfield_sb(sb, box.ymax, format, 'f')) return LW_FAILURE;
410  if (!corner_to_subfield_sb(sb, box.ymin, format, 'g')) return LW_FAILURE;
411  if (stringbuffer_aprintf(sb, "</datafield>") < 0) return LW_FAILURE;
412 
413  POSTGIS_DEBUG(2, "=> gbox_to_marc21_sb returns LW_SUCCESS");
414 
415  return LW_SUCCESS;
416 }
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:126
LWGEOM * lwgeom_from_gserialized(const GSERIALIZED *g)
Allocate a new LWGEOM from a GSERIALIZED.
Definition: gserialized.c:239
#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 can contain sub-geometries or not.
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 lwerror(const char *fmt,...)
Write a notice out to the error handler.
Definition: lwutil.c:190
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:145
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:203
#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:247
lwvarlena_t * stringbuffer_getvarlenacopy(stringbuffer_t *s)
Definition: stringbuffer.c:151
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