1 : /******************************************************************************
2 : * $Id: ogrbnadataparser.c
3 : *
4 : * Project: BNA Parser
5 : * Purpose: Parse a BNA record
6 : * Author: Even Rouault, even dot rouault at mines dash paris dot org
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2007, Even Rouault
10 : *
11 : * Permission is hereby granted, free of charge, to any person obtaining a
12 : * copy of this software and associated documentation files (the "Software"),
13 : * to deal in the Software without restriction, including without limitation
14 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 : * and/or sell copies of the Software, and to permit persons to whom the
16 : * Software is furnished to do so, subject to the following conditions:
17 : *
18 : * The above copyright notice and this permission notice shall be included
19 : * in all copies or substantial portions of the Software.
20 : *
21 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 : * DEALINGS IN THE SOFTWARE.
28 : ****************************************************************************/
29 :
30 : #include "ogrbnaparser.h"
31 :
32 : #include "cpl_conv.h"
33 : #include "cpl_string.h"
34 :
35 442 : void BNA_FreeRecord(BNARecord* record)
36 : {
37 442 : if (record)
38 : {
39 : int i;
40 2210 : for(i=0;i<NB_MAX_BNA_IDS;i++)
41 : {
42 1768 : if (record->ids[i]) VSIFree(record->ids[i]);
43 1768 : record->ids[i] = NULL;
44 : }
45 442 : CPLFree(record->tabCoords);
46 442 : record->tabCoords = NULL;
47 442 : CPLFree(record);
48 : }
49 442 : }
50 :
51 0 : const char* BNA_FeatureTypeToStr(BNAFeatureType featureType)
52 : {
53 0 : switch (featureType)
54 : {
55 : case BNA_POINT:
56 0 : return "point";
57 : case BNA_POLYGON:
58 0 : return "polygon";
59 : case BNA_POLYLINE:
60 0 : return "polyline";
61 : case BNA_ELLIPSE:
62 0 : return "ellipse";
63 : default:
64 0 : return "unknown";
65 : }
66 : }
67 :
68 0 : void BNA_Display(BNARecord* record)
69 : {
70 : int i;
71 : fprintf(stderr, "\"%s\", \"%s\", \"%s\", %s\n",
72 0 : record->ids[0] ? record->ids[0] : "",
73 0 : record->ids[1] ? record->ids[1] : "",
74 0 : record->ids[2] ? record->ids[2] : "",
75 0 : BNA_FeatureTypeToStr(record->featureType));
76 0 : for(i=0;i<record->nCoords;i++)
77 0 : fprintf(stderr, "%f, %f\n", record->tabCoords[i][0], record->tabCoords[i][1]);
78 0 : }
79 :
80 : /*
81 : For a description of the format, see http://www.softwright.com/faq/support/boundary_file_bna_format.html
82 : and http://64.145.236.125/forum/topic.asp?topic_id=1930&forum_id=1&Topic_Title=how+to+edit+*.bna+files%3F&forum_title=Surfer+Support&M=False
83 : */
84 :
85 : /* The following lines are a valid BNA file (for this parser) :
86 : "PID1","SID1",1
87 : -0,0
88 : "PID2","SID2",-2, -1e-5,-1e2 ,-2,-2
89 : "PID3""a","SID3",4
90 : 5,5
91 : 6,5
92 : 6,6
93 : 5,5
94 : "PID4","SID4",2
95 : 10,10
96 : 5,4
97 :
98 : "PID4","SID4",2
99 :
100 : 10,10
101 : 5,4
102 : */
103 :
104 : /* The following lines are a valid BNA file (for this parser) :
105 : (small extract from ftp://ftp.ciesin.org/pub/census/usa/tiger/ct/bnablk/b09001.zip)
106 : "090010000.00099A","099A","090010000.00099A","blk",21
107 : -73.049337, 41.125000 -73.093761, 41.157780 -73.107900, 41.168300
108 : -73.106878, 41.166459 -73.095800, 41.146500 -73.114553, 41.146331
109 : -73.138922, 41.147993 -73.166200, 41.154200 -73.198497, 41.085988
110 : -73.232241, 41.029986 -73.229548, 41.030507 -73.183922, 41.041955
111 : -73.178678, 41.043239 -73.177951, 41.043417 -73.147888, 41.050781
112 : -73.118658, 41.057942 -73.052399, 41.074174 -73.024976, 41.080892
113 : -73.000000, 41.087010 -73.035597, 41.114420 -73.049337, 41.125000
114 : */
115 :
116 : /* We are (and must be) a bit tolerant : BNA files format has several variations */
117 : /* and most don't follow strictly the 'specification' */
118 : /* Extra spaces, tabulations or line feed are accepted and ignored */
119 : /* We allow one line format and several line format in the same file */
120 : /* We allow from NB_MIN_BNA_IDS to NB_MAX_BNA_IDS ids */
121 : /* We allow that couples of coordinates on the same line may be separated only by spaces */
122 : /* (instead of being separated by a comma) */
123 :
124 : #define STRING_NOT_TERMINATED "string not terminated when end of line occured"
125 : #define MISSING_FIELDS "missing fields"
126 : #define BAD_INTEGER_NUMBER_FORMAT "bad integer number format"
127 : #define BAD_FLOAT_NUMBER_FORMAT "bad float number format"
128 : #define PRIMARY_ID_MANDATORY "primary ID can't be empty or missing"
129 : #define STRING_EXPECTED "string expected"
130 : #define NUMBER_EXPECTED "number expected"
131 : #define INTEGER_NUMBER_EXPECTED "integer number expected"
132 : #define FLOAT_NUMBER_EXPECTED "float number expected"
133 : #define INVALID_GEOMETRY_TYPE "invalid geometry type"
134 : #define TOO_LONG_ID "too long id (> 256 characters)"
135 : #define MAX_BNA_IDS_REACHED "maximum number of IDs reached"
136 : #define NOT_ENOUGH_MEMORY "not enough memory for request number of coordinates"
137 : #define LINE_TOO_LONG "line too long"
138 :
139 : #define TMP_BUFFER_SIZE 256
140 : #define LINE_BUFFER_SIZE 1024
141 :
142 : enum
143 : {
144 : BNA_LINE_OK,
145 : BNA_LINE_EOF,
146 : BNA_LINE_TOO_LONG
147 : };
148 :
149 3049 : static int BNA_GetLine(char szLineBuffer[LINE_BUFFER_SIZE+1], FILE* f)
150 : {
151 3049 : char* ptrCurLine = szLineBuffer;
152 3049 : int nRead = VSIFRead(szLineBuffer, 1, LINE_BUFFER_SIZE, f);
153 3049 : szLineBuffer[nRead] = 0;
154 3049 : if (nRead == 0)
155 : {
156 : /* EOF */
157 73 : return BNA_LINE_EOF;
158 : }
159 :
160 2976 : int bFoundEOL = FALSE;
161 80639 : while (*ptrCurLine)
162 : {
163 77663 : if (*ptrCurLine == 0x0d || *ptrCurLine == 0x0a)
164 : {
165 2976 : bFoundEOL = TRUE;
166 2976 : break;
167 : }
168 74687 : ptrCurLine ++;
169 : }
170 2976 : if (!bFoundEOL)
171 : {
172 0 : if (nRead < LINE_BUFFER_SIZE)
173 0 : return BNA_LINE_OK;
174 : else
175 0 : return BNA_LINE_TOO_LONG;
176 : }
177 :
178 2976 : if (*ptrCurLine == 0x0d)
179 : {
180 2224 : if (ptrCurLine == szLineBuffer + LINE_BUFFER_SIZE - 1)
181 : {
182 : char c;
183 0 : nRead = VSIFRead(&c, 1, 1, f);
184 0 : if (nRead == 1)
185 : {
186 0 : if (c == 0x0a)
187 : {
188 : /* Do nothing */
189 : }
190 : else
191 : {
192 0 : VSIFSeek(f, -1, SEEK_CUR);
193 : }
194 : }
195 : }
196 2224 : else if (ptrCurLine[1] == 0x0a)
197 : {
198 2224 : VSIFSeek(f, ptrCurLine + 2 - (szLineBuffer + nRead), SEEK_CUR);
199 : }
200 : else
201 : {
202 0 : VSIFSeek(f, ptrCurLine + 1 - (szLineBuffer + nRead), SEEK_CUR);
203 : }
204 : }
205 : else /* *ptrCurLine == 0x0a */
206 : {
207 752 : VSIFSeek(f, ptrCurLine + 1 - (szLineBuffer + nRead), SEEK_CUR);
208 : }
209 2976 : *ptrCurLine = 0;
210 :
211 2976 : return BNA_LINE_OK;
212 : }
213 :
214 :
215 442 : BNARecord* BNA_GetNextRecord(FILE* f,
216 : int* ok,
217 : int* curLine,
218 : int verbose,
219 : BNAFeatureType interestFeatureType)
220 : {
221 : BNARecord* record;
222 : char c;
223 442 : int inQuotes = FALSE;
224 442 : int numField = 0;
225 442 : const char* ptrBeginningOfNumber = NULL;
226 442 : int exponentFound = 0;
227 442 : int exponentSignFound = 0;
228 442 : int dotFound = 0;
229 442 : int numChar = 0;
230 442 : const char* detailedErrorMsg = NULL;
231 442 : BNAFeatureType currentFeatureType = (BNAFeatureType) -1;
232 442 : int nbExtraId = 0;
233 : char tmpBuffer[NB_MAX_BNA_IDS][TMP_BUFFER_SIZE+1];
234 442 : int tmpBufferLength[NB_MAX_BNA_IDS] = {0, 0, 0};
235 : char szLineBuffer[LINE_BUFFER_SIZE + 1];
236 :
237 442 : record = (BNARecord*)CPLMalloc(sizeof(BNARecord));
238 442 : memset(record, 0, sizeof(BNARecord));
239 :
240 2607 : while (TRUE)
241 : {
242 3049 : numChar = 0;
243 3049 : (*curLine)++;
244 :
245 3049 : int retGetLine = BNA_GetLine(szLineBuffer, f);
246 3049 : if (retGetLine == BNA_LINE_TOO_LONG)
247 : {
248 0 : detailedErrorMsg = LINE_TOO_LONG;
249 0 : goto error;
250 : }
251 3049 : else if (retGetLine == BNA_LINE_EOF)
252 : {
253 : break;
254 : }
255 :
256 2976 : const char* ptrCurLine = szLineBuffer;
257 2976 : const char* ptrBeginLine = szLineBuffer;
258 :
259 2976 : if (*ptrCurLine == 0)
260 0 : continue;
261 :
262 74687 : while(1)
263 : {
264 77663 : numChar = ptrCurLine - ptrBeginLine;
265 77663 : c = *ptrCurLine;
266 77663 : if (c == 0) c = 10;
267 77663 : if (inQuotes)
268 : {
269 3700 : if (c == 10)
270 : {
271 0 : detailedErrorMsg = STRING_NOT_TERMINATED;
272 0 : goto error;
273 : }
274 3700 : else if (c == '"' && ptrCurLine[1] == '"')
275 : {
276 0 : if (tmpBufferLength[numField] == TMP_BUFFER_SIZE)
277 : {
278 0 : detailedErrorMsg = TOO_LONG_ID;
279 0 : goto error;
280 : }
281 0 : tmpBuffer[numField][tmpBufferLength[numField]++] = c;
282 :
283 0 : ptrCurLine++;
284 : }
285 3700 : else if (c == '"')
286 : {
287 740 : inQuotes = FALSE;
288 : }
289 : else
290 : {
291 2960 : if (tmpBufferLength[numField] == TMP_BUFFER_SIZE)
292 : {
293 0 : detailedErrorMsg = TOO_LONG_ID;
294 0 : goto error;
295 : }
296 2960 : tmpBuffer[numField][tmpBufferLength[numField]++] = c;
297 : }
298 : }
299 73963 : else if (c == ' ' || c == '\t')
300 : {
301 1182 : if (numField > NB_MIN_BNA_IDS + nbExtraId && ptrBeginningOfNumber != NULL)
302 : {
303 0 : do
304 : {
305 1182 : ptrCurLine++;
306 1182 : numChar = ptrCurLine - ptrBeginLine;
307 1182 : c = *ptrCurLine;
308 1182 : if (!(c == ' ' || c == '\t'))
309 1182 : break;
310 : } while(c);
311 1182 : if (c == 0) c = 10;
312 :
313 1182 : if (interestFeatureType == BNA_READ_ALL ||
314 : interestFeatureType == currentFeatureType)
315 : {
316 340 : record->tabCoords[(numField - nbExtraId - NB_MIN_BNA_IDS - 1) / 2]
317 340 : [1 - ((numField - nbExtraId) % 2)] =
318 340 : CPLAtof(ptrBeginningOfNumber);
319 : }
320 1182 : if (numField == NB_MIN_BNA_IDS + 1 + nbExtraId + 2 * record->nCoords - 1)
321 : {
322 0 : if (c != 10)
323 : {
324 0 : if (verbose)
325 : {
326 : CPLError(CE_Warning, CPLE_AppDefined,
327 : "At line %d, at char %d, extra data will be ignored!\n",
328 0 : *curLine, numChar+1);
329 : }
330 : }
331 0 : *ok = 1;
332 0 : return record;
333 : }
334 :
335 1182 : ptrBeginningOfNumber = NULL;
336 1182 : exponentFound = 0;
337 1182 : exponentSignFound = 0;
338 1182 : dotFound = 0;
339 1182 : numField++;
340 :
341 1182 : if (c == 10)
342 0 : break;
343 :
344 1182 : if (c != ',')
345 : {
346 : /* don't increment ptrCurLine */
347 1182 : continue;
348 : }
349 : }
350 : else
351 : {
352 : /* ignore */
353 : }
354 : }
355 77538 : else if (c == 10 || c == ',')
356 : {
357 : /* Eat a comma placed at end of line */
358 7733 : if (c == ',')
359 : {
360 4757 : const char* ptr = ptrCurLine+1;
361 9514 : while(*ptr)
362 : {
363 4757 : if (*ptr != ' ' && *ptr != '\t')
364 4757 : break;
365 0 : ptr++;
366 : }
367 4757 : if (*ptr == 0)
368 : {
369 0 : c = 10;
370 : }
371 : }
372 :
373 7733 : if (numField == 0)
374 : {
375 : /* Maybe not so mandatory.. Atlas MapMaker(TM) exports BNA files with empty primaryID */
376 : /*
377 : if (record->primaryID == NULL || *(record->primaryID) == 0)
378 : {
379 : detailedErrorMsg = PRIMARY_ID_MANDATORY;
380 : goto error;
381 : }
382 : */
383 : }
384 7364 : else if (numField == NB_MIN_BNA_IDS + nbExtraId)
385 : {
386 : int nCoords;
387 369 : if (ptrBeginningOfNumber == NULL)
388 : {
389 0 : detailedErrorMsg = INTEGER_NUMBER_EXPECTED;
390 0 : goto error;
391 : }
392 369 : nCoords = atoi(ptrBeginningOfNumber);
393 369 : if (nCoords == 0 || nCoords == -1)
394 : {
395 0 : detailedErrorMsg = INVALID_GEOMETRY_TYPE;
396 0 : goto error;
397 : }
398 369 : else if (nCoords == 1)
399 : {
400 72 : currentFeatureType = record->featureType = BNA_POINT;
401 72 : record->nCoords = 1;
402 : }
403 297 : else if (nCoords == 2)
404 : {
405 62 : currentFeatureType = record->featureType = BNA_ELLIPSE;
406 62 : record->nCoords = 2;
407 : }
408 235 : else if (nCoords > 0)
409 : {
410 205 : currentFeatureType = record->featureType = BNA_POLYGON;
411 205 : record->nCoords = nCoords;
412 : }
413 : else
414 : {
415 30 : currentFeatureType = record->featureType = BNA_POLYLINE;
416 30 : record->nCoords = -nCoords;
417 : }
418 :
419 369 : record->nIDs = NB_MIN_BNA_IDS + nbExtraId;
420 :
421 369 : if (interestFeatureType == BNA_READ_ALL ||
422 : interestFeatureType == currentFeatureType)
423 : {
424 : int i;
425 695 : for(i=0;i<NB_MAX_BNA_IDS;i++)
426 : {
427 556 : if (tmpBufferLength[i] && tmpBuffer[i][0])
428 : {
429 279 : record->ids[i] = (char*)CPLMalloc(tmpBufferLength[i] + 1);
430 279 : tmpBuffer[i][tmpBufferLength[i]] = 0;
431 279 : memcpy(record->ids[i], tmpBuffer[i], tmpBufferLength[i] + 1);
432 : }
433 : }
434 :
435 : record->tabCoords =
436 139 : (double(*)[2])VSIMalloc(record->nCoords * 2 * sizeof(double));
437 139 : if (record->tabCoords == NULL)
438 : {
439 0 : detailedErrorMsg = NOT_ENOUGH_MEMORY;
440 0 : goto error;
441 : }
442 : }
443 : }
444 6995 : else if (numField > NB_MIN_BNA_IDS + nbExtraId)
445 : {
446 6624 : if (ptrBeginningOfNumber == NULL)
447 : {
448 0 : detailedErrorMsg = FLOAT_NUMBER_EXPECTED;
449 0 : goto error;
450 : }
451 6624 : if (interestFeatureType == BNA_READ_ALL ||
452 : interestFeatureType == currentFeatureType)
453 : {
454 2870 : record->tabCoords[(numField - nbExtraId - NB_MIN_BNA_IDS - 1) / 2]
455 2870 : [1 - ((numField - nbExtraId) % 2)] =
456 2870 : CPLAtof(ptrBeginningOfNumber);
457 : }
458 6624 : if (numField == NB_MIN_BNA_IDS + 1 + nbExtraId + 2 * record->nCoords - 1)
459 : {
460 369 : if (c != 10)
461 : {
462 0 : if (verbose)
463 : {
464 : CPLError(CE_Warning, CPLE_AppDefined,
465 : "At line %d, at char %d, extra data will be ignored!\n",
466 0 : *curLine, numChar+1);
467 : }
468 : }
469 369 : *ok = 1;
470 369 : return record;
471 : }
472 : }
473 :
474 7364 : ptrBeginningOfNumber = NULL;
475 7364 : exponentFound = 0;
476 7364 : exponentSignFound = 0;
477 7364 : dotFound = 0;
478 7364 : numField++;
479 :
480 7364 : if (c == 10)
481 2607 : break;
482 : }
483 65048 : else if (c == '"')
484 : {
485 740 : if (numField < NB_MIN_BNA_IDS)
486 : {
487 738 : inQuotes = TRUE;
488 : }
489 4 : else if (numField >= NB_MIN_BNA_IDS && currentFeatureType == -1)
490 : {
491 2 : if (ptrBeginningOfNumber == NULL)
492 : {
493 2 : if (nbExtraId == NB_MAX_BNA_IDS - NB_MIN_BNA_IDS)
494 : {
495 0 : detailedErrorMsg = MAX_BNA_IDS_REACHED;
496 0 : goto error;
497 : }
498 2 : nbExtraId ++;
499 2 : inQuotes = TRUE;
500 : }
501 : else
502 : {
503 0 : detailedErrorMsg = BAD_INTEGER_NUMBER_FORMAT;
504 0 : goto error;
505 : }
506 : }
507 : else
508 : {
509 0 : detailedErrorMsg = NUMBER_EXPECTED;
510 0 : goto error;
511 : }
512 : }
513 : else
514 : {
515 64308 : if (numField < NB_MIN_BNA_IDS || (numField == NB_MIN_BNA_IDS + nbExtraId - 1))
516 : {
517 0 : detailedErrorMsg = STRING_EXPECTED;
518 0 : goto error;
519 : }
520 64308 : else if (numField == NB_MIN_BNA_IDS + nbExtraId)
521 : {
522 537 : if (c >= '0' && c <= '9')
523 : {
524 : }
525 60 : else if (c == '+' || c == '-')
526 : {
527 30 : if (ptrBeginningOfNumber != NULL)
528 : {
529 0 : detailedErrorMsg = BAD_INTEGER_NUMBER_FORMAT;
530 0 : goto error;
531 : }
532 : }
533 : else
534 : {
535 0 : detailedErrorMsg = BAD_INTEGER_NUMBER_FORMAT;
536 0 : goto error;
537 : }
538 537 : if (ptrBeginningOfNumber == NULL)
539 369 : ptrBeginningOfNumber = ptrCurLine;
540 : }
541 : else
542 : {
543 63771 : if (c >= '0' && c <= '9')
544 : {
545 : }
546 7198 : else if (c == '.')
547 : {
548 7198 : if (dotFound || exponentFound)
549 : {
550 0 : detailedErrorMsg = BAD_FLOAT_NUMBER_FORMAT;
551 0 : goto error;
552 : }
553 7198 : dotFound = 1;
554 : }
555 0 : else if (c == '+' || c == '-')
556 : {
557 0 : if (ptrBeginningOfNumber == NULL)
558 : {
559 : }
560 0 : else if (exponentFound)
561 : {
562 0 : if (exponentSignFound == 0 && ptrCurLine > ptrBeginLine &&
563 0 : (ptrCurLine[-1] == 'e' || ptrCurLine[-1] == 'E' ||
564 0 : ptrCurLine[-1] == 'd' || ptrCurLine[-1] == 'D'))
565 : {
566 0 : exponentSignFound = 1;
567 : }
568 : else
569 : {
570 0 : detailedErrorMsg = BAD_FLOAT_NUMBER_FORMAT;
571 0 : goto error;
572 : }
573 : }
574 : else
575 : {
576 0 : detailedErrorMsg = BAD_FLOAT_NUMBER_FORMAT;
577 0 : goto error;
578 : }
579 : }
580 0 : else if (c == 'e' || c == 'E' || c == 'd' || c == 'D')
581 : {
582 0 : if (ptrBeginningOfNumber == NULL ||
583 0 : !(ptrCurLine[-1] >= '0' && ptrCurLine[-1] <= '9') ||
584 : exponentFound == 1)
585 : {
586 0 : detailedErrorMsg = BAD_FLOAT_NUMBER_FORMAT;
587 0 : goto error;
588 : }
589 0 : exponentFound = 1;
590 : }
591 : else
592 : {
593 0 : detailedErrorMsg = BAD_FLOAT_NUMBER_FORMAT;
594 0 : goto error;
595 : }
596 63771 : if (ptrBeginningOfNumber == NULL)
597 7806 : ptrBeginningOfNumber = ptrCurLine;
598 : }
599 : }
600 73505 : ptrCurLine++;
601 : }
602 : }
603 :
604 73 : if (numField == 0)
605 : {
606 : /* End of file */
607 73 : *ok = 1;
608 73 : BNA_FreeRecord(record);
609 73 : return NULL;
610 : }
611 : else
612 : {
613 0 : detailedErrorMsg = MISSING_FIELDS;
614 : goto error;
615 : }
616 : error:
617 0 : if (verbose)
618 : {
619 0 : if (detailedErrorMsg)
620 : {
621 : CPLError(CE_Failure, CPLE_AppDefined,
622 : "Parsing failed at line %d, at char %d : %s!\n",
623 0 : *curLine, numChar+1, detailedErrorMsg);
624 : }
625 : else
626 : {
627 : CPLError(CE_Failure, CPLE_AppDefined,
628 : "Parsing failed at line %d, at char %d!\n",
629 0 : *curLine, numChar+1);
630 : }
631 : }
632 0 : BNA_FreeRecord(record);
633 0 : return NULL;
634 : }
|