1 /*
2 *
3 * XASTIR, Amateur Station Tracking and Information Reporting
4 * Copyright (C) 2000-2019 The Xastir Group
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program 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 this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 *
20 * Look at the README for more information on the program.
21 */
22
23 // include postgresql library for postgis support
24 #ifdef HAVE_POSTGIS
25 #include <libpq-fe.h>
26 // pg_type.h contains constants for OID values to use in paramTypes arrays
27 // in prepared queries.
28 #include <pg_type.h>
29 #endif // HAVE_POSTGIS
30
31 // mysql error library for mysql error code constants
32 #ifdef HAVE_MYSQL
33 #include <my_global.h>
34 #include <my_sys.h>
35 #include <mysql.h>
36 #include <errmsg.h>
37 #include <time.h>
38 #endif // HAVE_MYSQL
39
40
41 #ifdef HAVE_CONFIG_H
42 #include "config.h"
43 #endif // HAVE_CONFIG_H
44
45 // Some systems don't have strtof
46 #ifndef HAVE_STRTOF
47 #define strtof(a,b) atof(a)
48 #endif
49
50 #include "snprintf.h"
51
52 #include <stdio.h>
53 #include <ctype.h>
54 #include <Xm/XmAll.h>
55 #include <stdlib.h>
56 #include <unistd.h>
57 #include <sys/types.h>
58
59 #include "database.h"
60 #include "main.h"
61 #include "util.h"
62 #include "xastir.h"
63 #include "db_gis.h"
64
65 #ifdef HAVE_DB
66 /* db_gis.c
67 *
68 * Functions supporting connections to databases, including GIS enabled
69 * databases that hold OpenGIS objects and can apply spatial indicies.
70 *
71 * XASTIR GIS database code is separated into three layers
72 *
73 * 1) Supporting XASTIR logic (ui elements, cad integration,
74 * map drawing, etc).
75 * 2a) Generic db storage/retrieval code - wrappers for layer 3
76 * 2b) Connection management code
77 * 3a) DBMS specific db storage/retrieval code for spatial databases
78 * 3b) DBMS specific db storage/retrieval code for non-spatial databases
79 *
80 * Data structures in an underlying database can be considered as a fourth
81 * level.
82 *
83 * Code for layers 2 and 3 is in this file.
84 *
85 * Layer 2 functions should be extern and called from elsewhere to
86 * perform spatial database operations. Xastir shouldn't need to care
87 * if an underlying database has spatial support or not for simple data.
88 * Some functionality might require spatial object support and might be
89 * included only if a spatial database is available. Thus 3b code may only
90 * support a subset of the 2a functions, while 3a code should support all
91 * 2a functions.
92 * Layer 2a wrappers should take and return values in xastir coordinates,
93 * and convert them to decimal degrees to pass on to layer 3. Likewise
94 * return values from layer 3 to layer 2a should be in decimal degrees,
95 * limiting the number of different places at which the xastir/decimal
96 * degree conversion code needs to be invoked. This would not be true if
97 * data are fed directly from decimal degree feeds into the database, so
98 * there may also be a need for layer 2 functions that deal only with
99 * latitude and longitude in decimal degrees.
100 *
101 * Layer 3 functions should not be extern and should only be called
102 * by layer 2 functions from within this file.
103 * Layer 3 functions should take and return values in decimal degrees.
104 * Xastir objects should be passed down into layer 3, as doing
105 * so should make code easier to maintain (but harder to extend) than using
106 * generic structures for transport of data between layers 2 and 3.
107 * Passing a station struct from layer 2 to 3 makes layer 2 a very simple
108 * wrapper, but requires new layer 3 code to write station data to a map
109 * layer rather than to a DataRow (to, for example, prepare a layer of
110 * temperature data at points for analysis and generation of a temperature
111 * grid.) [Using generic structures for transport would let the layer 3
112 * code remain unchanged while layer 2 functions are added or extended, but
113 * requires added maintainance to synchronise xastir structs, the generic
114 * structs, and database structures.]
115 *
116 * A spatially enabled database is expected to support OpenGIS spatial
117 * objects and be able to apply spatial indicies to the data. A
118 * non-spatially enabled database is expected to hold coordinates using
119 * separate fields for latitude and longitude. Layer 3 functions that
120 * interact with a spatial database will need to convert decimal degrees
121 * to well known text (WKT, and perhaps also well known binary, WKB)
122 * representations. Layer 3 functions that interact with non-spatially
123 * enabled databases can just pass raw latitudes and longitudes.
124 *
125 * All spatial data are expected to be in WGS84 projection EPSG:4326
126 *
127 * Support for five sets of underlying database schema elements is envisioned
128 * - a very simple station at point and time table
129 * - schema elements to support CAD objects with arbitrary associated data
130 * tables.
131 * - a schema capable of holding the full range of aprs data using spatial
132 * elements (Points, Polygons, etc).
133 * - full support for APRSWorld tables (using latitude and longitude fields
134 * rather than spatial elements).
135 * - arbitrary tables with schema discovery for arbitrary GIS databases
136 * such as Tiger data.
137 * The first three of these will require schema version awareness and will
138 * produce compatability/database lifecycle issues.
139 *
140 * Descriptions of how to make connections to databases are stored in
141 * connection descriptors. Connection descriptors describe the DBMS, whether
142 * the database has/lacks spatial support, the schema type (simple,
143 * simple+cad, xastir full, APRSWorld, etc for the database, and connection
144 * parameters (server, user, database). The layer 2/3 separation is intended
145 * to allow functions (layer 2) to be called from within xastir (layer 1)
146 * without the need to test which function to call for which dbms. Some
147 * functions may be schema specific, others may be able to use any of
148 * several different schemas. Connections can be opened from a database
149 * descriptor, and more than one descriptor can point to the same database.
150 * (Thus a single MySQL database may contain simple xastir tables, xastir
151 * CAD object tables, and APRSWorld tables, but two different descriptors
152 * would be used to define connections to talk to the APRSWorld tables and
153 * the simple+cad tables within what MySQL considers one schema. A given
154 * version of xastir will expect a particular version or range of versions
155 * for database schemas - an older version of xastir may expect fields that
156 * no longer exist in a database created for a newer version of xastir and
157 * vice versa.
158 *
159 * Data selected from a spatial database might be brought into xastir as
160 * stations just like an internet feed or findu fetch trail query, as
161 * editable CAD objects, or as map layers.
162 */
163
164 /******************* DATABASE SUPPORT IS EXPERIMENTAL ***********************/
165 /**************** CODE IN THIS FILE MAY CHANGE AT ANY TIME ******************/
166 // Layer 3 declarations
167
168 // xastir_dbms_type is used in interface_gui.c to set up cb_items to populate
169 // database picklist. Define and internationalise here.
170 char xastir_dbms_type[4][XASTIR_DB_DESCRIPTOR_MAX_SIZE+1] = {"","MySQL (lat/long)","Postgresql/Postgis","MySQL Spatial"} ;
171 // xastir_schema_type is used in interface_gui.c to set up cb_item to populate
172 // schema type picklist Sql_Database_schema_type_data. Define and internationalize here.
173 char xastir_schema_type[5][XASTIR_SCHEMA_DESCRIPTOR_MAX_SIZE+1] = {"","Xastir Simple","Xastir CAD","Xastir Full","APRSWorld"} ;
174
175 const char *POSTGIS_TIMEFORMAT = "%Y-%m-%d %H:%M:%S%z";
176 const char *MYSQL_TIMEFORMAT = "%Y-%m-%d %H:%M:%S";
177
178 /*
179 // store integer values for picklist items, but use localized strings on picklists
180 char xastir_dbms_type[3][XASTIR_DB_DESCRIPTOR_MAX_SIZE+1]; // array of xastir database type strings
181 xastir_snprintf(&xastir_dbms_type[DB_MYSQL][0],
182 XASTIR_DB_DESCRIPTOR_MAX_SIZE,
183 "%s",langcode("XADBMST001"));
184 xastir_snprintf(&xastir_dbms_type[DB_POSTGIS][0],
185 sizeof(&xastir_dbms_type[DB_POSTGIS][0]),
186 "%s", langcode("XADBMST002"));
187 xastir_snprintf(&xastir_dbms_type[DB_MYSQL_SPATIAL][0],
188 sizeof(&xastir_dbms_type[DB_MYSQL_SPATIAL][0]),
189 "%s",langcode("XADBMST003"));
190
191 char xastir_schema_type[4][XASTIR_SCHEMA_DESCRIPTOR_MAX_SIZE+1]; // array of xastir schema type strings
192 xastir_snprintf(xastir_schema_type[XASTIR_SCHEMA_SIMPLE],
193 sizeof(xastir_schema_type[XASTIR_SCHEMA_SIMPLE][0]),
194 "%s",langcode ("XASCHEMA01"));
195 xastir_snprintf(xastir_schema_type[XASTIR_SCHEMA_CAD][0],
196 sizeof(xastir_schema_type[XASTIR_SCHEMA_CAD][0]),
197 "%s", langcode("XASCHEMA02"));
198 xastir_snprintf(xastir_schema_type[XASTIR_SCHEMA_COMPLEX][0],
199 sizeof(xastir_schema_type[XASTIR_SCHEMA_COMPLEX][0]),
200 "%s", langcode("XASCHEMA03"));
201 xastir_snprintf(xastir_schema_type[XASTIR_SCHEMA_APRSWORLD],
202 sizeof(xastir_schema_type[XASTIR_SCHEMA_APRSWORLD][0]),
203 "%s", langcode("XASCHEMA04"));
204 */
205
206 #ifdef HAVE_SPATIAL_DB
207 #ifdef HAVE_POSTGIS
208 int storeStationToGisDbPostgis(Connection *aDbConnection, DataRow *aStation);
209 int storeCadToGisDbPostgis(Connection *aDbConnection, CADRow *aCadObject);
210 int storeStationSimplePointToGisDbPostgis(Connection *aDbConnection, DataRow *aStation);
211 int testXastirVersionPostgis(Connection *aDbConnection);
212 int getAllSimplePositionsPostgis(Connection *aDbConnection);
213 int getAllSimplePositionsPostgisInBoundingBox(Connection *aDbConnection, char* str_e_long, char* str_w_long, char* str_n_lat, char* str_s_lat);
214 //PGconn postgres_conn_struct[MAX_DB_CONNECTIONS];
215 #endif /* HAVE_POSTGIS*/
216 #ifdef HAVE_MYSQL_SPATIAL
217 int storeStationToGisDbMysql(Connection *aDbConnection, DataRow *aStation);
218 int storeCadToGisDbMysql(Connection *aDbConnection, CADRow *aCadObject);
219 int storeStationSimplePointToGisDbMysql(Connection *aDbConnection, DataRow *aStation);
220 int getAllSimplePositionsMysqlSpatial(Connection *aDbConnection);
221 int getAllCadFromGisDbMysql(Connection *aDbConnection);
222 int getAllSimplePositionsMysqlSpatialInBoundingBox(Connection *aDbConnection, char* str_e_long, char* str_w_long, char* str_n_lat, char* str_s_lat);
223 #endif /* HAVE_MYSQL_SPATIAL */
224 #endif /* HAVE_SPATIAL_DB */
225 //Connection connection_struc[MAX_DB_CONNECTIONS];
226 Connection connections[MAX_IFACE_DEVICES];
227 int connections_initialized = 0;
228 #ifdef HAVE_MYSQL
229 //MYSQL mysql_conn_struct, *mysql_connection = &mysql_conn_struct;
230 //MYSQL mcs[MAX_DB_CONNECTIONS];
231 Connection dbc_struct, *dbc = &dbc_struct;
232 int testXastirVersionMysql(Connection *aDbConnection);
233 int storeStationSimplePointToDbMysql(Connection *aDbConnection, DataRow *aStation);
234 int getAllSimplePositionsMysql(Connection *aDbConnection);
235 int getAllSimplePositionsMysqlInBoundingBox(Connection *aDbConnection, char *str_e_long, char *str_w_long, char *str_n_lat, char *str_s_lat);
236 int storeStationToDbMysql(Connection *aDbConnection, DataRow *aStation);
237 void mysql_interpret_error(int errorcode, Connection *aDbConnection);
238 #endif /* HAVE_MYSQL*/
239
240 // Layer 2a: Generic GIS db storage code. ************************************
241 // Wrapper functions for actual DBMS specific actions
242
243 #ifdef HAVE_SPATIAL_DB
244
245 // ******** Functions that require spatialy enabled database support *********
246
247
248
249
250
251 /* function storeStationToGisDb()
252 * Stores the information about a station and its most recent position
253 * to a spatial database.
254 * @param aDbConnection generic database connection to the database in
255 * which the station information is to be stored.
256 * @param aStation the station to store.
257 * @returns 0 on failure, 1 on success. On failure, stores error message
258 * in connection.
259 */
storeStationToGisDb(Connection * aDbConnection,DataRow * aStation)260 int storeStationToGisDb(Connection *aDbConnection, DataRow *aStation)
261 {
262 int returnvalue = 0;
263 if (aDbConnection==NULL || aStation==NULL)
264 {
265 return returnvalue;
266 }
267 // This function is dbms agnostic, and hands the call off to a
268 // function for the relevant database type. That function picks the
269 // relevant schema and either handles the query or passes it on to
270 // a function to handle that schema.
271
272
273
274 switch (aDbConnection->type)
275 {
276 #ifdef HAVE_POSTGIS
277 case DB_POSTGIS :
278 returnvalue = storeStationToGisDbPostgis(aDbConnection, aStation);
279 break;
280 #endif /* HAVE_POSTGIS */
281 #ifdef HAVE_MYSQL_SPATIAL
282 case DB_MYSQL_SPATIAL :
283 returnvalue = storeStationToGisDbMysql(aDbConnection, aStation);
284 break;
285 #endif /* HAVE_MYSQL_SPATIAL */
286 #ifdef HAVE_MYSQL
287 case DB_MYSQL :
288 returnvalue = storeStationToDbMysql(aDbConnection, aStation);
289 break;
290 #endif /* HAVE_MYSQL*/
291 }
292 return returnvalue;
293 }
294
295
296
297
298
299
300 /* function storeCadToGisDb()
301 * Stores current data about objects (including CAD objects) and their
302 * most recent positions to a spatial database. Objects are treated as
303 * points
304 */
storeCadToGisDb(Connection * aDbConnection,CADRow * aCadObject)305 int storeCadToGisDb(Connection *aDbConnection, CADRow *aCadObject)
306 {
307 int returnvalue = 0;
308 if (aDbConnection==NULL || aCadObject==NULL)
309 {
310 return returnvalue;
311 }
312
313
314 // check that connection has cad support in schema
315 return returnvalue;
316 }
317
318
319
320
321
322
323 /* function storeStationTrackToGisDb()
324 * Stores information about a station and track of all recieved positions from
325 * that station (including weather information if present) to a spatial
326 * database.
327 * @param aDbConnection generic database connection to the database in
328 * which the station information is to be stored.
329 * @param aStation the station to store.
330 * @returns 0 on failure, 1 on success. On failure, stores error message
331 * in connection.
332 */
storeStationTrackToGisDb(Connection * aDbConnection,DataRow * aStation)333 int storeStationTrackToGisDb(Connection *aDbConnection, DataRow *aStation)
334 {
335 int returnvalue = 0;
336 if (aDbConnection==NULL || aStation==NULL)
337 {
338 return returnvalue;
339 }
340
341 return returnvalue;
342 }
343
344
345
346
347
348
349 #endif /* HAVE_SPATIAL_DB */
350
351
352
353
354
355 // ***** Functions that do not require spatialy enabled database support ******
356 // Include "Simple" in these function names. They should only deal with point
357 // data, not polygons or complex spatial objects. Station positions and times
358 // demarking implicit tracks should be ok.
359
360
361
362
363
364
365 /* function storeStationSimpleToGisDb()
366 * Stores basic information about a station and its most recent position
367 * to a spatial database. Stores only callsign, most recent position,
368 * and time. Intended for testing and simple logging uses.
369 * Underlying table should have structure:
370 * create table simpleStation (
371 * simpleStationId int primary key not null auto_increment
372 * station varchar(9) not null, // max_callsign
373 * time date not null default now(),
374 * position POINT // or latitude float, longitude float for simple db.
375 * );
376 **** or perhaps it should be an APRSWorld table?? ****
377 **** or perhaps it should be an APRSWorld table, but with POINT when supported?? ****
378 *
379 * ********* generalize to lat/lon fields or position POINT. ******
380 * @param aDbConnection generic database connection to the database in
381 * which the station information is to be stored.
382 * @param aStation the station to store.
383 * @returns 0 on failure, 1 on success. On failure, stores error message
384 * in connection.
385 */
storeStationSimpleToGisDb(Connection * aDbConnection,DataRow * aStation)386 int storeStationSimpleToGisDb(Connection *aDbConnection, DataRow *aStation)
387 {
388 int returnvalue = 0;
389 int triedDatabase = 0;
390 if (debug_level & 4096)
391 {
392 fprintf(stderr,"in storeStationSimpleToGisDb() ");
393 }
394 if (aDbConnection==NULL || aStation==NULL)
395 {
396 return returnvalue;
397 }
398 if (aStation->data_via == DATA_VIA_DATABASE)
399 {
400 if (debug_level & 4096)
401 {
402 fprintf(stderr,"skipping station heard from Database\n");
403 }
404 returnvalue = 1;
405 return returnvalue;
406 }
407 if (debug_level & 4096)
408 {
409 fprintf(stderr,"with connection->type: %d\n",aDbConnection->type);
410 }
411
412 switch (aDbConnection->type)
413 {
414 #ifdef HAVE_POSTGIS
415 case DB_POSTGIS :
416 returnvalue = storeStationSimplePointToGisDbPostgis(aDbConnection, aStation);
417 triedDatabase++;
418 break;
419 #endif /* HAVE_POSTGIS */
420 #ifdef HAVE_MYSQL_SPATIAL
421 case DB_MYSQL_SPATIAL :
422 returnvalue = storeStationSimplePointToGisDbMysql(aDbConnection, aStation);
423 triedDatabase++;
424 break;
425 #endif /* HAVE_MYSQL_SPATIAL */
426 #ifdef HAVE_MYSQL
427 case DB_MYSQL :
428 returnvalue = storeStationSimplePointToDbMysql(aDbConnection, aStation);
429 triedDatabase++;
430 break;
431 #endif /* HAVE_MYSQL*/
432 }
433 if (triedDatabase==0)
434 {
435 }
436 return returnvalue;
437 }
438
439
440
441
442
443
444 /* function getAllSimplePositions()
445 * Given a database connection, return all simple station positions stored in
446 * that database.
447 */
getAllSimplePositions(Connection * aDbConnection)448 int getAllSimplePositions(Connection *aDbConnection)
449 {
450 int returnvalue = 0;
451 int triedDatabase = 0;
452 if (aDbConnection==NULL)
453 {
454 return returnvalue;
455 }
456 if (debug_level & 4096)
457 {
458 fprintf(stderr,"in getAllSimplePositions ");
459 fprintf(stderr,"with aDbConnection->type %d\n",aDbConnection->type);
460 }
461
462 switch (aDbConnection->type)
463 {
464 #ifdef HAVE_POSTGIS
465 case DB_POSTGIS :
466 //fprintf(stderr,"connection [%p]\n",aDbConnection);
467 //fprintf(stderr,"connection->phandle [%p]\n",aDbConnection->phandle);
468 returnvalue = getAllSimplePositionsPostgis(aDbConnection);
469 triedDatabase++;
470 break;
471 #endif /* HAVE_POSTGIS */
472 #ifdef HAVE_MYSQL_SPATIAL
473 case DB_MYSQL_SPATIAL :
474 returnvalue = getAllSimplePositionsMysqlSpatial(aDbConnection);
475 triedDatabase++;
476 break;
477 #endif /* HAVE_MYSQL_SPATIAL */
478 #ifdef HAVE_MYSQL
479 case DB_MYSQL :
480 returnvalue = getAllSimplePositionsMysql(aDbConnection);
481 triedDatabase++;
482 break;
483 #endif /* HAVE_MYSQL*/
484 }
485 if (triedDatabase==0)
486 {
487 }
488 return returnvalue;
489 }
490
491
492
493
494
495 /* function getAllSimplePositionsInBoundingBox()
496 * Given a database connection and a bounding box, return all simple station
497 * positions stored in that database that fall within the bounds of the box.
498 * Takes eastern, western, northern, and southern bounds of box in xastir
499 * coordinates.
500 */
getAllSimplePositionsInBoundingBox(Connection * aDbConnection,int east,int west,int north,int south)501 int getAllSimplePositionsInBoundingBox(Connection *aDbConnection, int east, int west, int north, int south)
502 {
503 int returnvalue = 0;
504 int triedDatabase = 0;
505 char str_e_long[11];
506 char str_n_lat[10];
507 char str_w_long[11];
508 char str_s_lat[10];
509 if (aDbConnection==NULL)
510 {
511 return returnvalue;
512 }
513 // convert from xastir coordinates to decimal degrees
514 convert_lon_l2s(east, str_e_long, sizeof(str_e_long), CONVERT_DEC_DEG);
515 convert_lat_l2s(north, str_n_lat, sizeof(str_n_lat), CONVERT_DEC_DEG);
516 convert_lon_l2s(west, str_w_long, sizeof(str_w_long), CONVERT_DEC_DEG);
517 convert_lat_l2s(south, str_s_lat, sizeof(str_s_lat), CONVERT_DEC_DEG);
518 switch (aDbConnection->type)
519 {
520 #ifdef HAVE_POSTGIS
521 case DB_POSTGIS :
522 returnvalue = getAllSimplePositionsPostgisInBoundingBox(aDbConnection,str_e_long,str_w_long,str_n_lat,str_s_lat);
523 triedDatabase++;
524 break;
525 #endif /* HAVE_POSTGIS */
526 #ifdef HAVE_MYSQL_SPATIAL
527 case DB_MYSQL_SPATIAL :
528 returnvalue = getAllSimplePositionsMysqlSpatialInBoundingBox(aDbConnection,str_e_long,str_w_long,str_n_lat,str_s_lat);
529 triedDatabase++;
530 break;
531 #endif /* HAVE_MYSQL_SPATIAL */
532 #ifdef HAVE_MYSQL
533 case DB_MYSQL :
534 returnvalue = getAllSimplePositionsMysqlInBoundingBox(aDbConnection,str_e_long,str_w_long,str_n_lat,str_s_lat);
535 triedDatabase++;
536 break;
537 #endif /* HAVE_MYSQL*/
538 }
539 if (triedDatabase==0)
540 {
541 }
542 return returnvalue;
543 }
544
545
546 // Layer 2b: Connection managment. *******************************************
547 /* It should be possible to maintain a list of an arbitrary number of defined
548 * data sources of different types, and to have an arbitrary number of
549 * connections to these data sources open at the same time.
550 *
551 * Some issues: How to handle login credentials for databases? Request on
552 * connection? How to perform multiple operations with the same datasource
553 * (e.g. logging to the database from feeds while querying CAD objects).
554 * Probably want to be able to store password, request password on connect,
555 * or use configuration file (e.g. my.ini) for password) - let user tune
556 * choices to environment.
557 *
558 * The existing interface code seems better suited to having a fixed number
559 * of interfaces with zero or one database connection associated with each
560 * interface than handing an arbitrary number of connections per interface.
561 */
562
563
564
565 // simple testing hardcoded database connection testing function
566 // remove this function and call in main.c when integration with
567 // interfaces is working.
568 // fill in password, uncomment, and uncomment code in main.c for
569 // simple database write test - writes station in n_first to simple mysql db
570 /*
571 ioparam simpleDbTest(void) {
572 ioparam test;
573 Connection conn;
574 int ok;
575 xastir_snprintf(test.device_name, sizeof(test.device_name), "Test Connection");
576 test.database_type = DB_MYSQL;
577 xastir_snprintf(test.device_host_name, sizeof(test.device_host_name), "localhost");
578 test.sp = 3306;
579 xastir_snprintf(test.database_username, sizeof(test.database_username), "xastir_test");
580 // hardcode a test password here for simple test
581 xastir_snprintf(test.device_host_pswd, sizeof(test.device_host_pswd), "hardcoded test password");
582 xastir_snprintf(test.database_schema, sizeof(test.database_schema), "xastir");
583 test.database_schema_type = XASTIR_SCHEMA_SIMPLE;
584 xastir_snprintf(test.database_unix_socket, sizeof(test.database_unix_socket), "/var/lib/mysql/mysql.sock");
585
586 got_conn=openConnection(&test, conn);
587 ok = storeStationSimpleToGisDb(&conn, n_first);
588
589 return test;
590 }
591 */
592
593
initConnections()594 int initConnections()
595 {
596 int x;
597 if (debug_level & 4096)
598 {
599 fprintf(stderr,"initConnections()\n");
600 }
601 for (x=0; x<MAX_IFACE_DEVICES; x++)
602 {
603 initAConnection(&connections[x], x);
604 }
605 if (debug_level & 4096)
606 {
607 for (x=0; x<MAX_IFACE_DEVICES; x++)
608 {
609 #ifdef HAVE_POSTGIS
610 fprintf(stderr,"Initialized connection %d [%p] type=%d phandle=[%p]\n",x,&connections[x],connections[x].type,connections[x].phandle);
611 #endif /* HAVE_POSTGIS */
612 #ifdef HAVE_MYSQL
613 fprintf(stderr,"Initialized connection %d mhandle=[%p]\n",x,&connections[x].mhandle);
614 #endif /* HAVE_MYSQL */
615 }
616 }
617 return 1;
618 }
619
620 /* Function initAConnections()
621 * Given a connection structure, initialize the storage for the
622 * database connections, link the connection to the relevant
623 * interface, and set default values for other parameters. */
initAConnection(Connection * connection,int x)624 int initAConnection(Connection *connection, int x)
625 {
626 int y;
627 connection->descriptor = &devices[x];
628 connection->type = 0; // assign no type by default
629 connection->interface_number = x; // so we can reference port_data[] from a connection
630 // without knowing the connection's position in
631 // connections[]
632 // malloc for the PGconn will cause segfault on trying to
633 // open the connection
634 #ifdef HAVE_POSTGIS
635 connection->phandle = (PGconn*)malloc(sizeof(PGconn*));
636 #endif /* HAVE_POSTGIS */
637 #ifdef HAVE_MYSQL
638 //connection->mhandle = (MYSQL)malloc(sizeof(MYSQL));
639 mysql_init(&connection->mhandle);
640 #endif /* HAVE_MYSQL */
641 for(y=0; y<MAX_CONNECTION_ERROR_MESSAGE; y++)
642 {
643 connection->errormessage[y]=' ';
644 }
645 connection->errormessage[MAX_CONNECTION_ERROR_MESSAGE-1]='\0';
646 if (debug_level & 4096)
647 {
648 fprintf(stderr,"initAConnection() [%d]\n",x);
649 }
650 return 1;
651 }
652
653 /* Function openConnection()
654 * Opens the specified database connection.
655 * @param anIface a database connection description (host username etc).
656 * @param connection a generic database connection for which the
657 * appropriate MySQL or Postgresql connection handle will be used
658 * for the open connection on success.
659 * @returns 0 on any error, 1 for successful connection
660 * on connection failure, returns 0 and sets error message in
661 * the connection descriptor.
662 */
openConnection(ioparam * anIface,Connection * connection)663 int openConnection(ioparam *anIface, Connection *connection)
664 {
665 int returnvalue = 0;
666 int connection_made = 0;
667 #ifdef HAVE_POSTGIS
668 char connection_string[900];
669 int connected; // status of connection polling loop
670 time_t start_time;
671 PGconn *postgres_connection;
672 PostgresPollingStatusType poll;
673 #endif /* HAVE_POSTGIS */
674 #ifdef HAVE_MYSQL
675 unsigned long client_flag = 0; // parameter used for mysql connection, is normally 0.
676 unsigned int port; // port to make connection on
677 #endif /* HAVE_MYSQL */
678
679
680 if (anIface==NULL)
681 {
682 fprintf(stderr,"Null iface\n");
683 return returnvalue;
684 }
685 if (anIface==NULL || connection==NULL)
686 {
687 fprintf(stderr,"Null connection\n");
688 return returnvalue;
689 }
690 if (debug_level & 4096)
691 {
692 fprintf(stderr,"opening connection [%p] \n",connection);
693 }
694 // #ifdef HAVE_MYSQL
695 // switch (anIface->database_type) {
696 // #ifdef HAVE_MYSQL_SPATIAL
697 // case DB_MYSQL_SPATIAL :
698 // #endif /* HAVE_MYSQL_SPATIAL */
699 // #ifdef HAVE_MYSQL
700 // case DB_MYSQL :
701 // #endif /* HAVE_MYSQL */
702 // // instantiate the MYSQL structure for the connection
703 // //fprintf(stderr,"calling mysql_init\n");
704 // //connection->mhandle = mysql_init(&connection->mhandle);
705 // //fprintf(stderr,"called mysql_init\n");
706 // }
707 // #endif /* HAVE_MYSQL */
708 // clear any existing error message
709 xastir_snprintf(anIface->database_errormessage, sizeof(anIface->database_errormessage), " ");
710 if (debug_level & 4096)
711 {
712 fprintf(stderr,"Entering openConnection with anIface [%p] and conn [%p]\n",anIface,connection);
713 }
714
715 connection->type = anIface->database_type;
716 //connection->descriptor = anIface;
717
718 if (connections_initialized == 0)
719 {
720 connections_initialized = initConnections();
721 connections_initialized = 1;
722 }
723
724 // TODO: need some sort of connection listener to handle reconnection attempts when a connection fails...
725
726 // try to open connection
727 if (!(anIface==NULL))
728 {
729 switch (anIface->database_type)
730 {
731 #ifdef HAVE_POSTGIS
732 case DB_POSTGIS :
733 if (debug_level & 4096)
734 {
735 fprintf(stderr,"Opening Connection to a Postgresql/Postgis database.\n");
736 }
737 // If type is postgis, connect to postgis database.
738 // build connection string from parameters
739 xastir_snprintf(connection_string, sizeof(connection_string), \
740 "host=%s user=%s password=%s dbname=%s port=%d", \
741 anIface->device_host_name, anIface->database_username, anIface->device_host_pswd, anIface->database_schema, anIface->sp);
742 // Use nonblocking connection (connectStart and connectPoll)
743 //connection->phandle = PQconnectStart(connection_string);
744 postgres_connection = PQconnectStart(connection_string);
745 //if (connection->phandle == NULL) {
746 if (postgres_connection == NULL)
747 {
748 xastir_snprintf(anIface->database_errormessage, sizeof(anIface->database_errormessage), "Insufficient memory to open connection.");
749 }
750 else
751 {
752 connected = 0;
753 // can connect, run PQ_connect_poll loop
754 // Note: xastir needs to decide when to time out
755 start_time = sec_now();
756 statusline("Connecting to Postgresql database",1);
757 while ((connected==0) & (sec_now()<(start_time+30)))
758 {
759 // need to add a timer to polling loop
760 //poll = PQconnectPoll(connection->phandle);
761 poll = PQconnectPoll(postgres_connection);
762 if (poll == PGRES_POLLING_FAILED || poll == PGRES_POLLING_OK)
763 {
764 connected = 1;
765 }
766 // add connection status feedback here if desired
767 }
768 //if (PQstatus(connection->phandle)==CONNECTION_OK) {
769 if (PQstatus(postgres_connection)==CONNECTION_OK)
770 {
771 if (debug_level & 4096)
772 {
773 fprintf(stderr,"Connected to Postgresql database on %s\n",anIface->device_host_name);
774 }
775 // connection successfull
776 connection->phandle = postgres_connection;
777 connection->type=DB_POSTGIS;
778 //connection->descriptor = anIface;
779 xastir_snprintf(connection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, " ");
780 connection_made = 1;
781 }
782 else
783 {
784 // connection attempt failed
785 fprintf(stderr,"Failed to connect to Postgresql database on %s\n",anIface->device_host_name);
786 fprintf(stderr,"Postgres Error: %s\n", PQerrorMessage(postgres_connection));
787 xastir_snprintf(anIface->database_errormessage, sizeof(anIface->database_errormessage), "Unable to make Postgresql connection %s. %s", PQerrorMessage(postgres_connection), connection_string);
788 }
789 }
790 break;
791 #endif /* HAVE_POSTGIS */
792 #ifdef HAVE_MYSQL_SPATIAL
793 case DB_MYSQL_SPATIAL :
794 // if type is mysql (=>4.1), connect to mysql database
795 if (debug_level & 4096)
796 {
797 fprintf(stderr,"Opening connection to a MySQL (spatial) database.\n");
798 }
799 if (&connection->mhandle == NULL)
800 {
801 // insufficient memory to initalize a new database handle
802 xastir_snprintf(anIface->database_errormessage, sizeof(anIface->database_errormessage), "Insufficient memory to open connection.");
803 }
804 else
805 {
806 port = anIface->sp;
807 statusline("Connecting to MySQL database",1);
808 if (debug_level & 4096)
809 {
810 fprintf(stderr,"Opening connection to %s.\n",anIface->device_host_name);
811 }
812 mysql_real_connect(&connection->mhandle, anIface->device_host_name, anIface->database_username, anIface->device_host_pswd, anIface->database_schema, port, anIface->database_unix_socket, client_flag);
813 //MYSQL *mysql_real_connect(MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned long client_flag)
814 if (&connection->mhandle == NULL)
815 {
816 // unable to establish connection
817 xastir_snprintf(anIface->database_errormessage, sizeof(anIface->database_errormessage), "Unable to establish connection: %s", mysql_error(&connection->mhandle));
818 fprintf(stderr,"Failed to connect to MySQL database on %s\n",anIface->device_host_name);
819 fprintf(stderr, "MySQL Error: %s", mysql_error(&connection->mhandle));
820 }
821 else
822 {
823
824 // mysql_real_connect is coming back with non-null failed connection.
825
826 // connected to database
827 // make sure error message for making connection is empty.
828 xastir_snprintf(anIface->database_errormessage, sizeof(anIface->database_errormessage), " ");
829 xastir_snprintf(connection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, " ");
830
831 // ping the server
832 if (mysql_ping(&connection->mhandle)==0)
833 {
834 fprintf(stderr,"mysql ping ok [0]\n");
835 connection_made = 1;
836 // store connection information
837 connection->type = DB_MYSQL_SPATIAL;
838 //connection->descriptor = anIface;
839 if (debug_level & 4096)
840 {
841 fprintf(stderr,"Connected to MySQL database, connection stored\n");
842 }
843 }
844 else
845 {
846 fprintf(stderr,"mysql ping failed [1]\n");
847 fprintf(stderr,"Can't connect to MySQL database: Can't ping server.\n");
848 xastir_snprintf(anIface->database_errormessage, sizeof(anIface->database_errormessage), "Unable to ping MySQL server. Server may be down. Check connection paramenters.");
849 }
850 }
851 }
852 break;
853 #endif /* HAVE_MYSQL_SPATIAL */
854 #ifdef HAVE_MYSQL
855 case DB_MYSQL :
856 // if type is mysql (<4.1), connect to mysql database
857 if (debug_level & 4096)
858 {
859 fprintf(stderr,"Opening connection to a MySQL database.\n");
860 }
861 if (&connection->mhandle == NULL)
862 {
863 // insufficient memory to initalize a new database handle
864 xastir_snprintf(anIface->database_errormessage, sizeof(anIface->database_errormessage), "Insufficient memory to open connection.");
865 fprintf(stderr,"Insufficient memory to open mysql connection [mysql_init(*MYSQL) returned null].\n");
866 }
867 else
868 {
869 client_flag = CLIENT_COMPRESS;
870 port = anIface->sp;
871 // **** fails if database_unix_socket doesn't exist
872 mysql_real_connect(&connection->mhandle, anIface->device_host_name, anIface->database_username, anIface->device_host_pswd, anIface->database_schema, port, anIface->database_unix_socket, client_flag);
873 if (&connection->mhandle == NULL)
874 {
875 fprintf(stderr,"Unable to establish connection to MySQL database\nHost: %s Schema: %s Username: %s\n",anIface->device_host_name, anIface->database_schema, anIface->database_username);
876 // unable to establish connection
877 xastir_snprintf(anIface->database_errormessage, sizeof(anIface->database_errormessage), "Unable to establish MySQL connection. Host: %s Username: %s Password: %s Schema %s Port: %d", anIface->device_host_name, anIface->database_username, anIface->device_host_pswd, anIface->database_schema, port);
878 fprintf(stderr,"Failed to connect to MySQL database on %s\n",anIface->device_host_name);
879 fprintf(stderr, "MySQL Error: %s", mysql_error(&connection->mhandle));
880 }
881 else
882 {
883 fprintf(stderr,"Connected to MySQL database on %s\n",anIface->device_host_name);
884 // connected to database
885 // make sure error message for making connection is empty.
886 xastir_snprintf(anIface->database_errormessage, sizeof(anIface->database_errormessage), " ");
887 xastir_snprintf(connection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, " ");
888
889 // ping the server
890 if (mysql_ping(&connection->mhandle)==0)
891 {
892 fprintf(stderr,"mysql ping ok [0]\n");
893 connection_made = 1;
894 // store connection information
895 connection->type = DB_MYSQL;
896 //connection->descriptor = anIface;
897 if (debug_level & 4096)
898 {
899 fprintf(stderr,"Connected to MySQL database, connection stored\n");
900 }
901 }
902 else
903 {
904 fprintf(stderr,"mysql ping failed [1]\n");
905 xastir_snprintf(anIface->database_errormessage, sizeof(anIface->database_errormessage), "Unable to ping MySQL server. Server may be down. Check connection paramenters.");
906 fprintf(stderr,"Can't connect to MySQL database: Can't ping server.\n");
907 }
908 }
909 }
910 break;
911 #endif /* HAVE_MYSQL*/
912 } /* end switch */
913 } /* end test for null interface */
914
915 if (connection_made==1)
916 {
917 if (debug_level & 4096)
918 {
919 fprintf(stderr,"Connection made: ");
920 fprintf(stderr,"connection->type [%d]\n",connection->type);
921 }
922 if (testConnection((Connection*)connection)==True)
923 {
924 returnvalue = 1;
925 statusline("Connected to database",1);
926 }
927 else
928 {
929 statusline("Incompatable database schema",1);
930 fprintf(stderr,"Connection OK, but incompatable schema. [%s]\n",connection->errormessage);
931 xastir_snprintf(anIface->database_errormessage, sizeof(anIface->database_errormessage), "%s",connection->errormessage);
932 closeConnection(connection,-1);
933 //free(connection);
934 }
935 }
936 else
937 {
938 // Detailed error message should have been returned above, but make sure
939 // there is at least a minimal failure message regardless of the problem.
940 statusline("Failed to connect to database",1);
941 fprintf(stderr,"Failed to make database connection.\n");
942 //free(connection); // not pointing to the right thing ??
943 port_data[connection->interface_number].status = DEVICE_ERROR;
944 }
945 return returnvalue;
946 }
947
948
949
950
951
952 /* Function closeConnection()
953 * Closes the specified database connection.
954 * @param aDbConnection a generic database connection handle.
955 */
closeConnection(Connection * aDbConnection,int port_number)956 int closeConnection(Connection *aDbConnection, int port_number)
957 {
958 //ioparam db = aDbConnection->descriptor;
959 fprintf(stderr,"Closing connection on port %d\n",port_number);
960 if (aDbConnection==NULL)
961 {
962 return 0;
963 }
964 // free up connection resources
965 switch (aDbConnection->type)
966 {
967 #ifdef HAVE_POSTGIS
968 case DB_POSTGIS :
969 fprintf(stderr,"Connection type is postgis.\n");
970 // if type is postgis, close connection to postgis database
971 if (aDbConnection->phandle!=NULL)
972 {
973 if (port_data[port_number].status==DEVICE_UP)
974 {
975 PQfinish(aDbConnection->phandle);
976 }
977 //free(aDbConnection->phandle);
978 }
979 break;
980 #endif /* HAVE_POSTGIS */
981 #ifdef HAVE_MYSQL_SPATIAL
982 case DB_MYSQL_SPATIAL :
983 // if type is mysql, close connection to mysql database
984 if (&aDbConnection->mhandle!=NULL)
985 {
986 if (debug_level & 4096)
987 {
988 fprintf(stderr,"Connection type to close is mysql spatial.\n");
989 fprintf(stderr,"mysql_stat [%s]\n",mysql_stat(&aDbConnection->mhandle));
990 }
991 mysql_close(&aDbConnection->mhandle);
992 //free(aDbConnection->mhandle);
993 }
994 break;
995 #endif /* HAVE_MYSQL_SPATIAL */
996 #ifdef HAVE_MYSQL
997 case DB_MYSQL :
998 fprintf(stderr,"Connection type is mysql.\n");
999 // if type is mysql, close connection to mysql database
1000 if (&aDbConnection->mhandle!=NULL)
1001 {
1002 mysql_close(&aDbConnection->mhandle);
1003 //free(aDbConnection->mhandle);
1004 }
1005 break;
1006 #endif /* HAVE_MYSQL*/
1007 }
1008
1009 return 1;
1010 }
1011
1012
1013
1014
1015
1016 /* Tests a database connection to see if the server is responding.
1017 * @param aDbConnection pointer to a generic connection handle.
1018 * @returns 0 on any error, 1 for successful ping.
1019 */
pingConnection(Connection * aDbConnection)1020 int pingConnection(Connection *aDbConnection)
1021 {
1022 int returnvalue = True;
1023 int dbreturn;
1024 #ifdef HAVE_POSTGIS
1025 ConnStatusType psql_status;
1026 #endif /* HAVE_POSTGIS */
1027
1028 if (aDbConnection==NULL)
1029 {
1030 return 0;
1031 }
1032
1033 if (debug_level & 4096)
1034 {
1035 fprintf(stderr,"Pinging database server type=[%d]\n",aDbConnection->type);
1036 //} else {
1037 //fprintf(stderr,"Pinging database server.\n");
1038 }
1039
1040 switch (aDbConnection->type)
1041 {
1042 #ifdef HAVE_POSTGIS
1043 case DB_POSTGIS:
1044 returnvalue = False;
1045 // is the connection open [required]
1046 if (aDbConnection->phandle!=NULL)
1047 {
1048 psql_status = PQstatus(aDbConnection->phandle);
1049 if (psql_status!=CONNECTION_OK)
1050 {
1051 xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Postgresql connection failed");
1052 fprintf(stderr, "PQstatus returned CONNECTION_BAD, probably unable to connect to server.\n");
1053
1054 }
1055 else
1056 {
1057 if (debug_level & 4096)
1058 {
1059 fprintf(stderr, "PQstatus returned CONNECTION_OK.\n");
1060 }
1061 returnvalue = True;
1062 }
1063 }
1064 break;
1065 #endif /* HAVE_POSTGIS */
1066 #ifdef HAVE_MYSQL_SPATIAL
1067 case DB_MYSQL_SPATIAL:
1068 returnvalue = False;
1069 // is the connection open [required]
1070 if (&aDbConnection->mhandle!=NULL)
1071 {
1072 // can we ping the server [required]
1073 dbreturn = mysql_ping(&aDbConnection->mhandle);
1074 if (dbreturn>0)
1075 {
1076 mysql_interpret_error(dbreturn, aDbConnection);
1077 fprintf(stderr, "MySQL Ping failed, probably unable to connect to server.\n");
1078 }
1079 else
1080 {
1081 if (debug_level & 4096)
1082 {
1083 fprintf(stderr, "MySQL Ping OK.\n");
1084 fprintf(stderr,"mysql_stat [%s]\n",mysql_stat(&aDbConnection->mhandle));
1085 }
1086 returnvalue = True;
1087 }
1088 }
1089 break;
1090 #endif /* HAVE_MYSQL_SPATIAL */
1091 #ifdef HAVE_MYSQL
1092 case DB_MYSQL:
1093 // is the connection open [required]
1094 if (&aDbConnection->mhandle != NULL)
1095 {
1096 dbreturn = mysql_ping(&aDbConnection->mhandle);
1097 if (dbreturn>0)
1098 {
1099 mysql_interpret_error(dbreturn, aDbConnection);
1100 fprintf(stderr, "MySQL Ping failed, probably unable to connect to server.\n");
1101 }
1102 else
1103 {
1104 if (debug_level & 4096)
1105 {
1106 fprintf(stderr, "MySQL Ping OK.\n");
1107 fprintf(stderr,"mysql_stat [%s]\n",mysql_stat(&aDbConnection->mhandle));
1108 }
1109 returnvalue = True;
1110 }
1111 }
1112 break;
1113 #endif /* HAVE_MYSQL*/
1114 }
1115 if (returnvalue==0)
1116 {
1117 fprintf(stderr,"\n[%s]\n",aDbConnection->errormessage);
1118 statusline("Database Ping Failed",1);
1119 port_data[aDbConnection->interface_number].status = DEVICE_ERROR;
1120 }
1121 return returnvalue;
1122 }
1123
1124
1125
1126
1127
1128 /* Tests a database connection and the underlying schema to see
1129 * if the connection is open, the schema version is supported by
1130 * this version of the code, and to see what permissions are
1131 * available */
testConnection(Connection * aDbConnection)1132 int testConnection(Connection *aDbConnection)
1133 {
1134 int returnvalue = True;
1135 int dbreturn;
1136 int major_version;
1137 int minor_version;
1138 char warning[100];
1139
1140 #ifdef HAVE_POSTGIS
1141 ConnStatusType psql_status;
1142 PGresult *result;
1143 const char *postgis_sql = "SELECT COUNT(*) FROM geometry_columns"; // test to see if schema used in connection has postgis support added
1144 #endif /* HAVE_POSTGIS */
1145 if (aDbConnection==NULL)
1146 {
1147 return 0;
1148 }
1149 xastir_snprintf(warning, 100, " "); // make sure warning is empty
1150 switch (aDbConnection->type)
1151 {
1152 #ifdef HAVE_POSTGIS
1153 case DB_POSTGIS:
1154 returnvalue = False;
1155 // is the connection open [required]
1156 if (aDbConnection->phandle!=NULL)
1157 {
1158 psql_status = PQstatus(aDbConnection->phandle);
1159 if (psql_status!=CONNECTION_OK)
1160 {
1161 xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Postgresql connection failed");
1162 fprintf(stderr, "PQstatus returned CONNECTION_BAD, probably unable to connect to server.\n");
1163
1164 }
1165 else
1166 {
1167 fprintf(stderr, "PQstatus returned CONNECTION_OK.\n");
1168 // which version of postgresql are we running
1169 dbreturn = PQserverVersion(aDbConnection->phandle);
1170 major_version = dbreturn / 10000;
1171 minor_version = (dbreturn - (major_version*10000)) / 100;
1172 fprintf(stderr,"Postgresql version [%d] %d.%d\n",dbreturn,major_version,minor_version);
1173
1174 // is the database spatially enabled [required]
1175 result = PQexec(aDbConnection->phandle,postgis_sql);
1176 if (result==NULL)
1177 {
1178 // PQexec probably couldn't allocate memory for the result set.
1179 xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Null result: %s\n",PQerrorMessage(aDbConnection->phandle));
1180 fprintf(stderr, "testConnection() Null result\nPostgresql Error : %s\n",PQerrorMessage(aDbConnection->phandle));
1181 }
1182 else
1183 {
1184 // PQexec returned a result, but it may not be valid, check to see.
1185 if (PQresultStatus(result)==PGRES_COMMAND_OK || PQresultStatus(result)==PGRES_TUPLES_OK)
1186 {
1187 // PQexec returned a valid result set, meaning that a geometry_types table exists.
1188
1189
1190 // are the needed tables present [required]
1191 // check schema type (simple, simple+cad, full, aprsworld)
1192 // check version of database schema for compatability
1193 if (testXastirVersionPostgis(aDbConnection)==1)
1194 {
1195 returnvalue = True;
1196 }
1197 // does the user have select privileges [required]
1198 // does the user have update privileges [optional]
1199 // does the user have inesrt privileges [optional]
1200 // does the user have delete privileges [optional]
1201 }
1202 else
1203 {
1204 // schema lacks a geometry_columns table, either schema or database lacks postgis support
1205 xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "No geometry_columns table found. Is PostGIS installed and added to this schema?\n");
1206 xastir_snprintf(warning, 100, "No geometry_columns table found. PostGIS may not be installed, or the schema may not have PostGIS support added.");
1207 fprintf(stderr, "No geometry_columns table found.\nPostGIS may not be installed, or the schema may not have PostGIS support added.\n");
1208 fprintf(stderr, "Postgresql Error : %s\n",PQerrorMessage(aDbConnection->phandle));
1209 }
1210 }
1211 }
1212 }
1213 break;
1214 #endif /* HAVE_POSTGIS */
1215 #ifdef HAVE_MYSQL_SPATIAL
1216 case DB_MYSQL_SPATIAL:
1217 returnvalue = False;
1218 // is the connection open [required]
1219 if (&aDbConnection->mhandle!=NULL)
1220 {
1221 // can we ping the server [required]
1222 dbreturn = mysql_ping(&aDbConnection->mhandle);
1223 if (dbreturn>0)
1224 {
1225 mysql_interpret_error(dbreturn, aDbConnection);
1226 fprintf(stderr,"Ping of mysql server failed.\n");
1227 xastir_snprintf(warning, 100, "%s",aDbConnection->errormessage);
1228
1229 }
1230 else
1231 {
1232 if (debug_level & 4096)
1233 {
1234 fprintf(stderr,"mysql_stat [%s]\n",mysql_stat(&aDbConnection->mhandle));
1235 }
1236 // is the database spatially enabled [required]
1237 // determine from db version >= 4.2
1238 // MySQL 4.1 is past end of life, 4.2 at end of life but still in widespread use, e.g. RHEL4 (in early 2008).
1239 // mysql_server_version is new to mysql 4.1, prepared queries stabilized in 4.2
1240 dbreturn = mysql_get_server_version(&aDbConnection->mhandle);
1241 if (dbreturn>0)
1242 {
1243 major_version = dbreturn / 10000;
1244 minor_version = (dbreturn - (major_version*10000)) / 100;
1245 if (major_version>=5 || (major_version==4 && minor_version >=2))
1246 {
1247 fprintf(stderr,"MySQL Server version %d.%d OK.\n",major_version,minor_version);
1248 // check version of database schema for compatability
1249 dbreturn = testXastirVersionMysql(aDbConnection);
1250 if (dbreturn==1)
1251 {
1252 fprintf(stderr,"Compatible Xastir database version found on server.\n");
1253
1254 // are the needed tables present [required]
1255 // check schema type (simple, simple+cad, full, aprsworld)
1256 // does the user have select privileges [required]
1257 // does the user have update privileges [optional]
1258 // does the user have insert privileges [optional]
1259 // does the user have delete privileges [optional]
1260 returnvalue = True;
1261 }
1262 else
1263 {
1264 fprintf(stderr,"Xastir database version on server is not compatable with this version of Xastir.\n");
1265 // aDbConnection->errormessage should have been set in testXastirVersionMysql
1266 xastir_snprintf(warning, 100, "%s",aDbConnection->errormessage);
1267 }
1268 }
1269 else
1270 {
1271 // version too low
1272 fprintf(stderr,"MySQL Server version %d.%d is too low and is not supported in Xastir.\n",major_version,minor_version);
1273 xastir_snprintf(warning, 100, "MySQL Server version %d.%d is too low and is not supported in Xastir.",major_version,minor_version);
1274 }
1275 }
1276 else
1277 {
1278 // ? mysql<4.1
1279 }
1280 }
1281 }
1282 break;
1283 #endif /* HAVE_MYSQL_SPATIAL */
1284 #ifdef HAVE_MYSQL
1285 case DB_MYSQL:
1286 // is the connection open [required]
1287 if (&aDbConnection->mhandle != NULL)
1288 {
1289 dbreturn = mysql_ping(&aDbConnection->mhandle);
1290 if (dbreturn>0)
1291 {
1292 mysql_interpret_error(dbreturn, aDbConnection);
1293 }
1294 else
1295 {
1296 if (debug_level & 4096)
1297 {
1298 fprintf(stderr,"mysql_stat [%s]\n",mysql_stat(&aDbConnection->mhandle));
1299 }
1300 // is the database spatially enabled [optional]
1301 // determine from db version >= 4.1
1302 #ifdef HAVE_MYSQL_SPATIAL
1303 // mysql_server_version is new to mysql 4.1
1304 dbreturn = mysql_get_server_version(&aDbConnection->mhandle);
1305 #endif /* HAVE_MYSQL_SPATIAL */
1306 // are the needed tables present [required]
1307 // check schema type (simple, simple+cad, aprsworld)
1308 // full requires objects, not supported here.
1309 // check version of database schema for compatability
1310 dbreturn = testXastirVersionMysql(aDbConnection);
1311 // does the user have select privileges [required]
1312 // does the user have update privileges [optional]
1313 // does the user have insert privileges [optional]
1314 // does the user have delete privileges [optional]
1315 }
1316 }
1317 break;
1318 #endif /* HAVE_MYSQL*/
1319 }
1320 if (returnvalue==0)
1321 {
1322 fprintf(stderr,"\n[%s]\n",aDbConnection->errormessage);
1323 xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Incompatable schema: %s",warning);
1324 fprintf(stderr,"\n[%s]\n",aDbConnection->errormessage);
1325 // Note: Don't close connection here, we haven't handed the error to the user yet.
1326 //closeConnection(aDbConnection,-1);
1327 fprintf(stderr,"\n[%s]\n",aDbConnection->errormessage);
1328 }
1329 return returnvalue;
1330 }
1331
1332
1333
1334
1335 // Layer 3: DBMS specific db storage code *************************************
1336 // Functions in this section should be local to this file and not exported
1337 // Export functions in section 2a above.
1338 //
1339 // Layer 3a: DBMS specific GIS db storage code ********************************
1340 // Functions supporting queries to specific types of GIS enabled databasesa
1341 //
1342
1343 #ifdef HAVE_SPATIAL_DB
1344
1345 #ifdef HAVE_POSTGIS
1346 // Postgis implementation of spatial database functions
1347
1348
1349
1350
1351
1352 /* postgresql+postgis implementation of storeStationToGisDb(). */
storeStationToGisDbPostgis(Connection * aDbConnection,DataRow * aStation)1353 int storeStationToGisDbPostgis(Connection *aDbConnection, DataRow *aStation)
1354 {
1355 int returnvalue = 0;
1356 //ioparam *device = aDbConnection->descriptor;
1357 // check type of schema to use (XASTIR simple, full or APRSWorld)
1358 switch (devices[aDbConnection->interface_number].database_schema_type)
1359 {
1360 case XASTIR_SCHEMA_SIMPLE :
1361 returnvalue = storeStationSimplePointToGisDbPostgis(aDbConnection,aStation);
1362 break;
1363 case XASTIR_SCHEMA_APRSWORLD :
1364 break;
1365 case XASTIR_SCHEMA_COMPLEX :
1366 break;
1367 // otherwise error message
1368 }
1369 return returnvalue;
1370 }
1371
1372
1373
1374
1375
1376 /* postgresql+postgis implementation of storeCadToGisDb(). */
storeCadToGisDbPostgis(Connection * aDbConnection,CADRow * aCadObject)1377 int storeCadToGisDbPostgis(Connection *aDbConnection, CADRow *aCadObject)
1378 {
1379 int returnvalue = 0;
1380
1381 return returnvalue;
1382 }
1383
1384
1385
1386
1387
1388 /* function storeStationSimplePointToGisDbPostgis()
1389 * Postgresql/Postgis implementation of wrapper storeStationSimplePointToGisDb().
1390 * Should only be called through wrapper function. Do not call directly.
1391 */
storeStationSimplePointToGisDbPostgis(Connection * aDbConnection,DataRow * aStation)1392 int storeStationSimplePointToGisDbPostgis(Connection *aDbConnection, DataRow *aStation)
1393 {
1394 int returnvalue = 0; // Default return value is failure.
1395 int ok; // Holds results of tests when building query.
1396 char wkt[MAX_WKT]; // well know text representation of latitude and longitude of point
1397 char timestring[101]; // string representation of the time heard or the current time
1398 char call_sign[(MAX_CALLSIGN*2)+1]; // temporary holding for escaped callsign
1399 char aprs_symbol[2]; // temporary holding for escaped aprs symbol
1400 char aprs_type[2]; // temporary holding for escaped aprs type
1401 char special_overlay[2]; // temporary holding for escaped overlay
1402 char origin[(MAX_CALLSIGN*2)+1]; // temporary holding for escaped origin
1403 char node_path[(NODE_PATH_SIZE*2)+1]; // temporary holding for escaped node_path
1404 char record_type[2]; // temporary holding for escaped record_type
1405 //PGconn *conn = aDbConnection->phandle;
1406 PGresult *prepared = NULL;
1407 PGresult *result = NULL;
1408 int count; // returned value from count query
1409 const int PARAMETERS = 9;
1410 // parameter arrays for prepared query
1411 const char *paramValues[PARAMETERS];
1412 // To use native Postgres POINT for position instead of postgis geometry point.
1413 //const Oid paramTypes[6] = { VARCHAROID, TIMESTAMPTZOID, POINTOID, VARCHAROID, VARCHAROID, VARCHAROID };
1414 //const Oid paramTypes[6] = { 1043, 1184, 600, 1043, 1043, 1043 };
1415 // Native postgres (8.2) geometries don't have spatial support as rich as Postgis extensions.
1416 // use postgis geometry Point instead:
1417 // lookup OID for geometry: select OID from pg_type where typname = 'geometry'; returns 19480
1418 //const Oid paramTypes[6] = { VARCHAROID, TIMESTAMPTZOID, 19480, VARCHAROID, VARCHAROID, VARCHAROID };
1419 //const Oid paramTypes[6] = { 1043, 1184, 19480, 1043, 1043, 1043 };
1420 // Value 18480 is probably installation specific, use unknownOID instead:
1421 //const Oid paramTypes[9] = { VARCHAROID, TIMESTAMPTZOID, UNKNOWNOID, VARCHAROID, VARCHAROID, VARCHAROID, VARCHAROID, VARCHAROID, VARCHAROID };
1422 const Oid paramTypes[9] = { 1043, 1184, 705, 1043, 1043, 1043, 1043, 1043, 1043 };
1423 const char *sql = "insert into simpleStation (station, transmit_time, position, symbol, overlay, aprstype, origin, record_type, node_path) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)";
1424 const char *StatementName = "InsertSimpleStation";
1425 const char *StatementExists = "select count(*) from pg_prepared_statements where name = 'InsertSimpleStation'";
1426
1427
1428 if (debug_level & 4096)
1429 {
1430 fprintf(stderr,"In postgres simple station insert\n");
1431 fprintf(stderr,"with connection [%p] \n",aDbConnection);
1432 fprintf(stderr,"connection->phandle [%p]\n",aDbConnection->phandle);
1433 }
1434 if (aDbConnection->phandle==NULL)
1435 {
1436 fprintf(stderr,"Trying to save station on null postgresql connection\n");
1437 return returnvalue;
1438 }
1439 if (PQserverVersion(aDbConnection->phandle)==0)
1440 {
1441 // no connection to server
1442 fprintf(stderr,"Trying to save station on closed postgresql connection\n");
1443 return returnvalue;
1444 }
1445 if (debug_level & 4096)
1446 {
1447 fprintf(stderr,"Postgresql version=%d\n",PQserverVersion(aDbConnection->phandle));
1448 }
1449
1450 // Check to see if this prepared statement exists in the current session
1451 // and create it if it does not.
1452 // Query adds connection overhead - should probably track with a global variable,
1453 // and query/recreate statment only on failure.
1454 ok = 0;
1455 // pg_prepared_statements system view added in postgresql 8.2
1456 if (PQserverVersion(aDbConnection->phandle)>80199)
1457 {
1458 result = PQexec(aDbConnection->phandle, "select count(*) from pg_prepared_statements where name = 'InsertSimpleStation'");
1459 result = PQexec(aDbConnection->phandle, StatementExists);
1460 if (result==NULL)
1461 {
1462 fprintf(stderr,"Postgres Check for Prepared Query exec Failed: %s\n", PQerrorMessage(aDbConnection->phandle));
1463 xastir_snprintf(aDbConnection->errormessage,MAX_CONNECTION_ERROR_MESSAGE,PQerrorMessage(aDbConnection->phandle));
1464 }
1465 else
1466 {
1467 count = 0;
1468 if (PQresultStatus(result) == PGRES_TUPLES_OK)
1469 {
1470 count = atoi(PQgetvalue(result,0,0));
1471 }
1472 if (count==0)
1473 {
1474 // Statement doesn't exist, so prepare it, let PQprepare report on any error that got us a NULL result.
1475 prepared = PQprepare(aDbConnection->phandle, StatementName, sql, PARAMETERS, paramTypes);
1476 if (PQresultStatus(prepared)==PGRES_COMMAND_OK)
1477 {
1478 ok = 1;
1479 }
1480 else
1481 {
1482 // error condition - can't prepare statement
1483 fprintf(stderr,"Postgres Prepare Query Failed: %s\n", PQerrorMessage(aDbConnection->phandle));
1484 xastir_snprintf(aDbConnection->errormessage,MAX_CONNECTION_ERROR_MESSAGE,PQerrorMessage(aDbConnection->phandle));
1485 exit(1);
1486
1487 }
1488 }
1489 else if (count==1)
1490 {
1491 // prepared statement exists, we can go ahead with query.
1492 ok = 1;
1493 }
1494 else
1495 {
1496 fprintf(stderr,"Postgres Check for Prepared Query getvalue (count=%d) failed: %s\n",count, PQresultErrorMessage(result));
1497 xastir_snprintf(aDbConnection->errormessage,MAX_CONNECTION_ERROR_MESSAGE,PQresultErrorMessage(result));
1498 }
1499 }
1500 }
1501 else
1502 {
1503 prepared = PQprepare(aDbConnection->phandle, StatementName, sql, PARAMETERS, paramTypes);
1504 ok = 1;
1505 }
1506 if (ok==1)
1507 {
1508 // native postgis POINT is (99.999 099.999) instead of POINT (99.999 099.999)
1509 // ok = xastirCoordToLatLongPosgresPoint(aStation->coord_lon, aStation->coord_lat, wkt);
1510 //
1511 // Prepared query is ready, get and fill in the parameter values
1512 // from the station provided, then fire the query.
1513 ok = xastirCoordToLatLongWKT(aStation->coord_lon, aStation->coord_lat, wkt);
1514 if (ok==1)
1515 {
1516 // Postgresql 8 documentation indicates that escape string should not be performed
1517 // when calling PQexecParams or its sibling routines, not explicit, but implication
1518 // is that PQexecPrepared with passed parameters is a sibling routine and we
1519 // shouldn't be running PQescapeStringConn() on the parameters.
1520 // If used, form would be:
1521 // PQescapeStringConn(conn,call_sign,aStation->call_sign,(MAX_CALLSIGN*2)+1,escape_error);
1522 xastir_snprintf(call_sign,MAX_CALLSIGN+1,"%s",aStation->call_sign);
1523 if (strlen(aStation->origin) > 0)
1524 {
1525 xastir_snprintf(origin,sizeof(origin),"%s",aStation->origin);
1526 }
1527 else
1528 {
1529 xastir_snprintf(origin,1,"%c",'\0');
1530 }
1531 xastir_snprintf(record_type,2,"%c",aStation->record_type);
1532 if (aStation->node_path_ptr==NULL)
1533 {
1534 xastir_snprintf(node_path,2," ");
1535 }
1536 else
1537 {
1538 xastir_snprintf(node_path,sizeof(node_path),"%s",aStation->node_path_ptr);
1539 }
1540 if (debug_level & 4096)
1541 {
1542 fprintf(stderr,"node_path (12345678901234567890123456789012345678901234567890123456)\n");
1543 fprintf(stderr,"node_path = [%s]\n",node_path);
1544 }
1545 // Get time in seconds, adjust to datetime
1546 // If aStation is my station or another unset sec_heard is
1547 // encountered, use current time instead. Conversely, use time
1548 // provided in sec_heard if sec_heard is an invalid time.
1549 get_iso_datetime(aStation->sec_heard,timestring,True,False);
1550
1551 // set parameter values to call, transmit_time, and position
1552 paramValues[0]=call_sign;
1553 paramValues[1]=timestring;
1554 paramValues[2]=wkt;
1555 if (aStation->aprs_symbol.aprs_symbol==NULL)
1556 {
1557 xastir_snprintf(aprs_symbol,2," ");
1558 paramValues[3]=&aprs_symbol;
1559 }
1560 else
1561 {
1562 xastir_snprintf(aprs_symbol,2,"%c",aStation->aprs_symbol.aprs_symbol);
1563 paramValues[3]=aprs_symbol;
1564 }
1565 if (aStation->aprs_symbol.special_overlay==NULL)
1566 {
1567 xastir_snprintf(special_overlay,2," ");
1568 paramValues[4]=&special_overlay;
1569 }
1570 else
1571 {
1572 xastir_snprintf(special_overlay,2,"%c",aStation->aprs_symbol.special_overlay);
1573 paramValues[4]=&special_overlay;
1574 }
1575 if (aStation->aprs_symbol.aprs_type==NULL)
1576 {
1577 xastir_snprintf(aprs_type,2," ");
1578 paramValues[5]=&aprs_type;
1579 }
1580 else
1581 {
1582 xastir_snprintf(aprs_type,2,"%c",aStation->aprs_symbol.aprs_type);
1583 paramValues[5]=aprs_type;
1584 }
1585 paramValues[6]=origin;
1586 paramValues[7]=record_type;
1587 paramValues[8]=node_path;
1588
1589 if (debug_level & 4096)
1590 {
1591 fprintf(stderr,"Inserting: Call: %s, Time: %s, Position: %s, Symbol:%s,%s,%s Origin:%s, Node_path:%s, Record type:%s\n",paramValues[0],paramValues[1],paramValues[2],paramValues[3],paramValues[4],paramValues[5],paramValues[6],paramValues[8],paramValues[7]);
1592 }
1593
1594 // send query
1595 result = PQexecPrepared(aDbConnection->phandle,StatementName,PARAMETERS,paramValues,NULL,NULL,POSTGRES_RESULTFORMAT_TEXT);
1596 if (PQresultStatus(result)!=PGRES_COMMAND_OK)
1597 {
1598 fprintf(stderr,"Postgres Insert query failed:%s\n",PQresultErrorMessage(result));
1599 // error, get error message.
1600 xastir_snprintf(aDbConnection->errormessage,MAX_CONNECTION_ERROR_MESSAGE,PQresultErrorMessage(result));
1601 }
1602 else
1603 {
1604 // query was successfull
1605 returnvalue=1;
1606 }
1607 }
1608 else
1609 {
1610 // problem with coordinates of station
1611 fprintf(stderr,"Unable to save station to Postgres db, Error converting latitude or longitude from xastir coordinates\n");
1612 xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Error converting latitude or longitude from xastir coordinates: %ld,%ld",aStation->coord_lat,aStation->coord_lon);
1613 }
1614 }
1615 if (result!=NULL)
1616 {
1617 PQclear(result);
1618 }
1619 if (prepared!=NULL)
1620 {
1621 PQclear(prepared);
1622 }
1623 return returnvalue;
1624 }
1625
1626
1627
1628
1629
1630 /* function testXastirVersionPostgis()
1631 * Postgresql/Postgis implementation of wrapper testXastirVersionPostgis().
1632 * Should only be called through wrapper function. Do not call directly.
1633 */
testXastirVersionPostgis(Connection * aDbConnection)1634 int testXastirVersionPostgis(Connection *aDbConnection)
1635 {
1636 int returnvalue = 0;
1637 int version_number;
1638 int compatable_series;
1639 const char sql[100] = "select version_number, compatable_series from version order by version_number desc limit 1";
1640 PGresult *result;
1641 PGconn *conn = aDbConnection->phandle;
1642
1643 result = PQexec(conn,sql);
1644 if (result==NULL)
1645 {
1646 // PQexec probably couldn't allocate memory for the result set.
1647 xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Null result: %s\n",PQerrorMessage(conn));
1648 fprintf(stderr, "testXastirVersionPostgis() Null result\nPostgresql Error : %s\n",PQerrorMessage(conn));
1649 }
1650 else
1651 {
1652 // PQexec returned a result, but it may not be valid, check to see.
1653 if (PQresultStatus(result)==PGRES_COMMAND_OK || PQresultStatus(result)==PGRES_TUPLES_OK)
1654 {
1655 if (PQntuples(result)!=1)
1656 {
1657 fprintf(stderr,"Version table doesn't appear to contain any rows.\n");
1658 xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Version table doesn't appear to contain any rows.");
1659 }
1660 else
1661 {
1662 version_number = atoi(PQgetvalue(result,0,0));
1663 compatable_series = atoi(PQgetvalue(result,0,1));
1664 if (version_number == XASTIR_SPATIAL_DB_VERSION)
1665 {
1666 returnvalue = 1;
1667 }
1668 else
1669 {
1670 if (version_number < XASTIR_SPATIAL_DB_VERSION && compatable_series == XASTIR_SPATIAL_DB_COMPATABLE_SERIES)
1671 {
1672 returnvalue = 1;
1673 fprintf(stderr,"Version in schema (%d) is compatible with this version of xastir (%d).\n",version_number,XASTIR_SPATIAL_DB_VERSION);
1674 }
1675 else
1676 {
1677 fprintf(stderr,"Version in schema (%d) is not compatible with this version of xastir (%d).\n",version_number,XASTIR_SPATIAL_DB_VERSION);
1678 xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Version in schema (%d) is not compatible with this version of xastir (%d).",version_number,XASTIR_SPATIAL_DB_VERSION);
1679 fprintf(stderr,"%s",aDbConnection->errormessage);
1680 returnvalue = 0;
1681 }
1682
1683 }
1684 }
1685
1686 }
1687 PQclear(result);
1688 }
1689 return returnvalue;
1690 }
1691
1692
1693
1694
1695
1696 /* function getAllSimplePositionsPostgis()
1697 * Postgresql/Postgis implementation of wrapper getAllSimplePositions().
1698 * Should only be called through wrapper function. Do not call directly.
1699 */
getAllSimplePositionsPostgis(Connection * aDbConnection)1700 int getAllSimplePositionsPostgis(Connection *aDbConnection)
1701 {
1702 int returnvalue = 0; // value to return from function, 1 for success, 0 for failure
1703 int row; // row counter for result set loop
1704 int station_count = 0; // number of new stations retrieved
1705 unsigned long x; // xastir coordinate for longitude
1706 unsigned long y; // xastir coordinate for latitide
1707 unsigned long u_long;
1708 unsigned long u_lat;
1709 char *s_lat[13]; // string latitude
1710 char *s_lon[13]; // string longitude
1711 float lat; // latitude converted from retrieved string
1712 float lon; // longitude converted from retrieved string
1713 const char *sql = "select station, symbol, overlay, aprstype, transmit_time, AsText(position), origin, record_type, node_path, X(position), Y(position) from simpleStation order by station, transmit_time asc";
1714 // station is column 0, symbol is column 1, etc.
1715 PGconn *conn = aDbConnection->phandle;
1716 char feedback[100];
1717 char lastcall[MAX_CALLSIGN+1]; //holds last retrieved callsign
1718 int exists; //shortcut to skip db check if currently retrieved callsign equals last retrieved callsign
1719 DataRow *p_new_station; // pointer to new station record
1720 DataRow *p_time; // pointer to new station record
1721 int skip;
1722 int points_this_station; // number of times this station has been heard.
1723 char empty[MAX_ALTITUDE];
1724 struct tm time;
1725 time_t sec;
1726 empty[0]='\0';
1727 xastir_snprintf(feedback,100,"Retrieving Postgis records\n");
1728 stderr_and_statusline(feedback);
1729 //fprintf(stderr,"connection->phandle [%p]\n",aDbConnection->phandle);
1730 // run query and retrieve result set
1731 PGresult *result = PQexec(conn,sql);
1732
1733 if (result==NULL)
1734 {
1735 // PQexec probably couldn't allocate memory for the result set.
1736 xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Null result: %s\n",PQerrorMessage(conn));
1737 fprintf(stderr, "getAllSimplePositionsPostgis() Null result\nPostgresql Error : %s\n",PQerrorMessage(conn));
1738 }
1739 else
1740 {
1741 // PQexec returned a result, but it may not be valid, check to see.
1742 if (PQresultStatus(result)==PGRES_COMMAND_OK || PQresultStatus(result)==PGRES_TUPLES_OK)
1743 {
1744 // PQexec returned a valid result set.
1745 xastir_snprintf(feedback,100,"Retrieving %i Postgis records\n",PQntuples(result));
1746 stderr_and_statusline(feedback);
1747 xastir_snprintf(lastcall,MAX_CALLSIGN+1," ");
1748 points_this_station = 0;
1749 for (row=0; row<PQntuples(result); row++)
1750 {
1751 // step through rows in result set and add each to xastir db as a minimal DataRow
1752 if (PQgetisnull(result,row,0))
1753 {
1754 // station name is null, skip.
1755 }
1756 else
1757 {
1758 // check if station exists
1759 p_new_station = NULL;
1760 exists = 0;
1761 // Shortcut check to see if this station has been loaded already
1762 // works as returned rows are ordered by station.
1763 // TODO: add_simple_station will not update the linked list of station properly,
1764 // when data from more than one database for the same station is loaded
1765 // and a more recently loaded database contains station data with timestamps
1766 // older than the timestamp from a previously loaded database.
1767 // This shows up as search_station_name failing to locate
1768 // existing stations and stations not being found on station info
1769 // (because they have expired from the list).
1770 // This will also be a problem if old data for stations heard live are
1771 // retrieved from a database while xastir is running.
1772 if (strcmp(PQgetvalue(result,row,0),lastcall)==0)
1773 {
1774 points_this_station++;
1775 exists = 1;
1776 }
1777 else
1778 {
1779 if (search_station_name(&p_new_station,PQgetvalue(result,row,0),1))
1780 {
1781 points_this_station++;
1782 exists = 1;
1783 }
1784 else
1785 {
1786 points_this_station=1;
1787 }
1788 }
1789 xastir_snprintf(lastcall,MAX_CALLSIGN+1,PQgetvalue(result,row,0));
1790 if (exists==1)
1791 {
1792 // This station allready exists as a DataRow in the xastir db.
1793 // Don't create a duplicate record, but add to the DataRow's track.
1794
1795 // check if it is a mobile station
1796 // We can't easily identify mobile stations from position position
1797 // becaue of rounding errors, therefore exclude stations that are likely to be fixed.
1798 // _/ = wx
1799 skip = 0;
1800 if ((PQgetvalue(result,row,1)[0]=='_') && (PQgetvalue(result,row,3)[0]=='/'))
1801 {
1802 skip = 1; // wx
1803 }
1804 if ((PQgetvalue(result,row,1)[0]=='-') && (PQgetvalue(result,row,3)[0]=='/'))
1805 {
1806 skip = 1; // house
1807 }
1808
1809
1810 if (skip==0)
1811 {
1812 // add to track
1813 if (search_station_name(&p_new_station,PQgetvalue(result,row,0),1))
1814 {
1815 if (points_this_station<3)
1816 {
1817 //existing station record needs to be added as a trailpoint
1818 (void)store_trail_point(p_new_station, p_new_station->coord_lon, p_new_station->coord_lat, p_new_station->sec_heard, empty, empty, empty, 0);
1819 }
1820
1821 // store this trail point
1822 lat = atof(PQgetvalue(result,row,10));
1823 lon = atof(PQgetvalue(result,row,9));
1824 if (strlen(PQgetvalue(result,row,4)) > 0)
1825 {
1826 strptime(PQgetvalue(result,row,4), "%Y-%m-%d %H:%M:%S%z", &time);
1827 sec = mktime(&time);
1828 }
1829 if(convert_to_xastir_coordinates( &u_long, &u_lat, lon, lat))
1830 {
1831 (void)store_trail_point(p_new_station, u_long, u_lat, sec, empty, empty, empty, 0);
1832 }
1833
1834 if (p_new_station->sec_heard < sec)
1835 {
1836 // update the station record to this position
1837 if(convert_to_xastir_coordinates(&u_long, &u_lat, lon, lat))
1838 {
1839 p_new_station->coord_lat = u_lat;
1840 p_new_station->coord_lon = u_long;
1841 p_new_station->sec_heard = sec;
1842 }
1843 }
1844 }
1845 }
1846
1847 }
1848 else
1849 {
1850 // This station isn't in the xastir db.
1851 //int add_simple_station(DataRow *p_new_station,char *station, char *origin, char *symbol, char *overlay, char *aprs_type, char *latitude, char *longitude, char *record_type, char *node_path, char *transmit_time) {
1852 //const char *sql = "select station, symbol, overlay, aprstype, transmit_time, AsText(position), origin, record_type, node_path, X(position), Y(position) from simpleStation order by station, transmit_time asc";
1853 add_simple_station(p_new_station,PQgetvalue(result,row,0), PQgetvalue(result,row,6), PQgetvalue(result,row,1), PQgetvalue(result,row,2), PQgetvalue(result,row,3), PQgetvalue(result,row,10), PQgetvalue(result,row,9), PQgetvalue(result,row,7), PQgetvalue(result,row,8), PQgetvalue(result,row,4), POSTGIS_TIMEFORMAT);
1854
1855 station_count ++;
1856 } // end else, new station
1857 } // end else, station is not null
1858 } // end for loop stepping through rows
1859 redo_list = (int)TRUE; // update active station lists
1860 xastir_snprintf(feedback,100,"Added %d stations from Postgis\n",station_count);
1861 stderr_and_statusline(feedback);
1862 }
1863 else
1864 {
1865 // sql query had a problem retrieving result set.
1866 xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "%s %s\n",PQresStatus(PQresultStatus(result)),PQerrorMessage(conn));
1867 fprintf(stderr, "getAllSimplePositionsPostgis() %s\nPostgresql Error : %s\n",PQresStatus(PQresultStatus(result)),PQerrorMessage(conn));
1868 }
1869 // done with result set, so free the resource.
1870 PQclear(result);
1871 }
1872 return returnvalue;
1873 }
1874
1875
1876
1877
1878
1879 /* function getAllSimplePositionsPostgisInBoundingBox()
1880 * Postgresql/Postgis implementation of wrapper getAllSimplePositionsInBoundingBox().
1881 * Should only be called through wrapper function. Do not call directly.
1882 */
getAllSimplePositionsPostgisInBoundingBox(Connection * aDbConnection,char * str_e_long,char * str_w_long,char * str_n_lat,char * str_s_lat)1883 int getAllSimplePositionsPostgisInBoundingBox(Connection *aDbConnection, char* str_e_long, char* str_w_long, char* str_n_lat, char* str_s_lat)
1884 {
1885 int returnvalue = 0;
1886 // set up prepared query with bounding box
1887 // postgis simple table uses POINT
1888 char sql[100] = "select call, transmit_time, position from simpleStation where ";
1889 PGconn *conn = aDbConnection->phandle;
1890
1891
1892 return returnvalue;
1893 }
1894
1895
1896
1897
1898 #endif /* HAVE_POSTGIS */
1899
1900 #ifdef HAVE_MYSQL_SPATIAL
1901 // Mysql 5 implementation of spatial database functions
1902
1903
1904
1905
1906
1907 /* function storeStationToGisDbMysql
1908 * MySQL implemenation of storeStationToGisDb
1909 * Should be private to db_gis.c
1910 * Should only be called through wrapper function. Do not call directly.
1911 * @param aDbConnection an exastir database connection struct describing
1912 * the connection.
1913 * @param aStation
1914 * Returns 0 for failure, 1 for success.
1915 * If failure, stores error message in aDbConnection->errormessage.
1916 */
storeStationToGisDbMysql(Connection * aDbConnection,DataRow * aStation)1917 int storeStationToGisDbMysql(Connection *aDbConnection, DataRow *aStation)
1918 {
1919 int returnvalue = 0;
1920 //ioparam *device = aDbConnection->descriptor;
1921 // check type of schema to use (XASTIR simple, full or APRSWorld)
1922 switch (devices[aDbConnection->interface_number].database_schema_type)
1923 {
1924 case XASTIR_SCHEMA_SIMPLE :
1925 returnvalue = storeStationSimplePointToGisDbMysql(aDbConnection,aStation);
1926 break;
1927 case XASTIR_SCHEMA_APRSWORLD :
1928 break;
1929 case XASTIR_SCHEMA_COMPLEX :
1930 break;
1931 // otherwise error message
1932 }
1933 return returnvalue;
1934 }
1935
1936
1937
1938
1939
1940
1941 /* function storeCadToGisDbMysql
1942 * MySQL implementation of storeCadToGisDbMysql
1943 * Should be private to db_gis.c
1944 * Should only be called through wrapper function. Do not call directly.
1945 * @param aDbConnection an exastir database connection struct describing
1946 * the connection.
1947 * @param aCadObject
1948 * Returns 0 for failure, 1 for success.
1949 * If failure, stores error message in aDbConnection->errormessage.
1950 */
storeCadToGisDbMysql(Connection * aDbConnection,CADRow * aCadObject)1951 int storeCadToGisDbMysql(Connection *aDbConnection, CADRow *aCadObject)
1952 {
1953 int returnvalue = 0;
1954
1955 return returnvalue;
1956 }
1957
1958
1959
1960
1961
1962
1963 /* support function for prepared statements
1964 int bind_mysql_string_parameter(MYSQL_BIND *bind, int bind_number, char* buffer, int provided_length, int buffer_length, my_bool is_null) {
1965 bind[bind_number]->buffer = buffer;
1966 bind[bind_number]->length = provided_length;
1967 bind[bind_number]->buffer_length = buffer_length;
1968 bind[bind_number]->buffer_type = MYSQL_TYPE_STRING
1969 bind[bind_number]->is_null = is_null;
1970 }
1971 */
1972
1973
1974
1975
1976
1977 /* function storeStationSimplePointToGisDbMysql()
1978 * MySQL implementation of wrapper storeStationSimplePointToGisDb().
1979 * Should be private to db_gis.c
1980 * Should only be called through wrapper function. Do not call directly.
1981 * @param aDbConnection an xastir database connection struct describing
1982 * the connection.
1983 * @param aStation
1984 * Returns 0 for failure, 1 for success.
1985 * On failure sets error message in aDbConnection->errormessage.
1986 */
storeStationSimplePointToGisDbMysql(Connection * aDbConnection,DataRow * aStation)1987 int storeStationSimplePointToGisDbMysql(Connection *aDbConnection, DataRow *aStation)
1988 {
1989 int returnvalue = 0;
1990 int mysqlreturn; // hold return value of mysql query
1991 int param_count; // check on the number of parameters present in the prepared statement
1992 int ok; // variable to store results of tests preparatory to firing query
1993 // temporary holding variables for bind buffers
1994 char wkt[MAX_WKT]; // well know text representation of latitude and longitude of point
1995 char aprs_symbol[2]; // temporary holding for escaped aprs symbol
1996 char aprs_type[2]; // temporary holding for escaped aprs type
1997 char special_overlay[2]; // temporary holding for escaped overlay
1998 char record_type[2]; // temporary holding for escaped record type
1999 char origin[MAX_CALLSIGN+1]; // temporary holding for escaped origin
2000 char node_path[NODE_PATH_SIZE+1]; // temporary holding for escaped node_path_ptr
2001 MYSQL_STMT *statement;
2002 // bind string lengths
2003 unsigned long call_sign_length;
2004 unsigned long wkt_length;
2005 unsigned long aprs_symbol_length;
2006 unsigned long aprs_type_length;
2007 unsigned long special_overlay_length;
2008 unsigned long origin_length;
2009 unsigned long record_type_length;
2010 unsigned long node_path_length;
2011 // time
2012 MYSQL_TIME timestamp;
2013 char timestring[100+1];
2014 time_t secs_now;
2015 struct tm *ts; // to convert time to component parts for bind.buffer_type MYSQL_TYPE_DATETIME
2016 // define prepared statement and matching bind array
2017 #define SQL "INSERT INTO simpleStationSpatial (station, transmit_time, position, symbol, overlay, aprstype, origin, record_type, node_path) VALUES (?,?,PointFromText(?),?,?,?,?,?,?)"
2018 MYSQL_BIND bind[9]; // bind array for prepared query.
2019 int parameters = 9;
2020 // Note:
2021 // bind[9], SQL "?????????", and param_count must all match value of parameters
2022 // nine bound parameters, nine question marks in the statement, and param_count returned as nine.
2023
2024 if (debug_level & 4096)
2025 {
2026 fprintf(stderr,"in storeStationSimplePointToGisDbMysql\n");
2027 fprintf(stderr,"with connection [%p] \n",aDbConnection);
2028 }
2029
2030 if (&aDbConnection->mhandle==NULL)
2031 {
2032 return returnvalue;
2033 }
2034
2035 statement = mysql_stmt_init(&aDbConnection->mhandle);
2036 if (!statement)
2037 {
2038 fprintf(stderr,"Unable to create mysql prepared statement. May be out of memmory.\n");
2039 }
2040 mysql_stmt_prepare(statement, SQL, strlen(SQL));
2041 if (!statement)
2042 {
2043 mysql_interpret_error(*mysql_error(&aDbConnection->mhandle),aDbConnection);
2044 }
2045 else
2046 {
2047 // test to make sure that statement has the correct number of parameters
2048 param_count=mysql_stmt_param_count(statement);
2049 if (param_count!=parameters)
2050 {
2051 fprintf(stderr,"Number of bound parameters %d does not match expected value %d\nFor query[%s]",param_count,parameters,SQL);
2052 fprintf(stderr, " %s\n", mysql_stmt_error(statement));
2053 }
2054 else
2055 {
2056 // set up the buffers
2057 memset(bind, 0, sizeof(bind));
2058
2059 bind[0].buffer = (char *)&aStation->call_sign;
2060 bind[0].length = &call_sign_length;
2061 bind[0].buffer_length = MAX_CALLSIGN;
2062 bind[0].buffer_type = MYSQL_TYPE_STRING;
2063 bind[0].is_null = 0;
2064
2065 bind[1].buffer = (char *)×tamp;
2066 bind[1].length = 0;
2067 bind[1].buffer_type = MYSQL_TYPE_DATETIME;
2068 bind[1].is_null = 0;
2069
2070 bind[2].buffer = (char *)&wkt;
2071 bind[2].length = &wkt_length;
2072 bind[2].buffer_length = MAX_WKT;
2073 bind[2].buffer_type = MYSQL_TYPE_STRING;
2074 bind[2].is_null = 0;
2075
2076 bind[3].buffer = (char *)&aprs_symbol;
2077 bind[3].length = &aprs_symbol_length;
2078 bind[3].buffer_length = 2;
2079 bind[3].buffer_type = MYSQL_TYPE_STRING;
2080 bind[3].is_null = 0;
2081
2082 bind[4].buffer = (char *)&special_overlay;
2083 bind[4].length = &special_overlay_length;
2084 bind[4].buffer_length = 2;
2085 bind[4].buffer_type = MYSQL_TYPE_STRING;
2086 bind[4].is_null = 0;
2087
2088 bind[5].buffer = (char *)&aprs_type;
2089 bind[5].length = &aprs_type_length;
2090 bind[5].buffer_length = 2;
2091 bind[5].buffer_type = MYSQL_TYPE_STRING;
2092 bind[5].is_null = 0;
2093
2094 bind[6].buffer = (char *)&origin; // segfaults with origin of zero length, otherwise writes bad data
2095 bind[6].length = &origin_length;
2096 bind[6].buffer_length = MAX_CALLSIGN;
2097 bind[6].buffer_type = MYSQL_TYPE_STRING;
2098 bind[6].is_null = 0;
2099
2100 bind[7].buffer = (char *)&record_type;
2101 bind[7].length = &record_type_length;
2102 bind[7].buffer_length = 2;
2103 bind[7].buffer_type = MYSQL_TYPE_STRING;
2104 bind[7].is_null = 0;
2105
2106 bind[8].buffer = (char *)&node_path;
2107 bind[8].length = &node_path_length;
2108 bind[8].buffer_length = NODE_PATH_SIZE;
2109 bind[8].buffer_type = MYSQL_TYPE_STRING;
2110 bind[8].is_null = 0;
2111
2112 ok = mysql_stmt_bind_param(statement,bind);
2113 if (ok!=0)
2114 {
2115 fprintf(stderr,"Error binding parameters to mysql prepared statement.\n");
2116 mysql_interpret_error(mysql_errno(&aDbConnection->mhandle),aDbConnection);
2117 fprintf(stderr,mysql_stmt_error(statement));
2118 }
2119 else
2120 {
2121
2122 // get call, time, and position
2123 // call is required
2124 if (aStation->call_sign!=NULL && strlen(aStation->call_sign)>0)
2125 {
2126 call_sign_length = strlen(aStation->call_sign);
2127
2128 // get time in seconds, adjust to datetime
2129 // If my station or another unset sec_heard is
2130 // encountered, use current time instead, use time
2131 // provided if it was invalid.
2132 get_iso_datetime(aStation->sec_heard,timestring,True,False);
2133 if ((int)aStation->sec_heard==0 )
2134 {
2135 secs_now = sec_now();
2136 ts = localtime(&secs_now);
2137 }
2138 else
2139 {
2140 ts = localtime(&aStation->sec_heard);
2141 }
2142 timestamp.year = ts->tm_year + 1900; // tm_year is from 1900
2143 timestamp.month = ts->tm_mon + 1; // tm_mon is from 0
2144 timestamp.day = ts->tm_mday; // tm_mday is from 1
2145 timestamp.hour = ts->tm_hour;
2146 timestamp.minute = ts->tm_min;
2147 timestamp.second = ts->tm_sec;
2148 ok = xastirCoordToLatLongWKT(aStation->coord_lon, aStation->coord_lat, wkt);
2149 if (ok==1)
2150 {
2151 wkt_length = strlen(wkt);
2152
2153
2154 if (aStation->aprs_symbol.aprs_symbol)
2155 {
2156 xastir_snprintf(aprs_symbol,2,"%c",aStation->aprs_symbol.aprs_symbol);
2157 }
2158 else
2159 {
2160 xastir_snprintf(aprs_symbol,2,"%c",'\0');
2161 }
2162 aprs_symbol_length = strlen(aprs_symbol);
2163
2164 if (aStation->aprs_symbol.aprs_type)
2165 {
2166 xastir_snprintf(aprs_type,2,"%c",aStation->aprs_symbol.aprs_type);
2167 }
2168 else
2169 {
2170 xastir_snprintf(aprs_type,2,"%c",'\0');
2171 }
2172 aprs_type_length = strlen(aprs_type);
2173
2174 if (aStation->aprs_symbol.special_overlay)
2175 {
2176 xastir_snprintf(special_overlay,2,"%c",aStation->aprs_symbol.special_overlay);
2177 }
2178 else
2179 {
2180 xastir_snprintf(special_overlay,2,"%c",'\0');
2181 }
2182 special_overlay_length = strlen(special_overlay);
2183
2184 if (aStation->origin)
2185 {
2186 xastir_snprintf(origin,MAX_CALLSIGN+1,"%s",aStation->origin);
2187 }
2188 else
2189 {
2190 //xastir_snprintf(origin,2,"%c",'\0');
2191 origin[0]='\0';
2192 }
2193 origin_length = strlen(origin);
2194
2195 if (aStation->record_type)
2196 {
2197 xastir_snprintf(record_type,2,"%c",aStation->record_type);
2198 }
2199 else
2200 {
2201 //xastir_snprintf(record_type,2,"%c",'\0');
2202 record_type[0]='\0';
2203 }
2204 record_type_length = strlen(record_type);
2205
2206 if (aStation->node_path_ptr)
2207 {
2208 if (debug_level & 4096)
2209 {
2210 fprintf(stderr,"node_path (12345678901234567890123456789012345678901234567890123456)\n");
2211 fprintf(stderr,"node_path = [%s]\n",aStation->node_path_ptr);
2212 }
2213 xastir_snprintf(node_path,NODE_PATH_SIZE+1,"%s",aStation->node_path_ptr);
2214 }
2215 else
2216 {
2217 //xastir_snprintf(node_path,2,"%c",'\0');
2218 node_path[0]='\0';
2219 }
2220 node_path_length = strlen(node_path);
2221
2222 // all the bound parameters should be available and correct
2223 if (debug_level & 4096)
2224 {
2225 fprintf(stderr,"saving station %s %d %d %d %d:%d:%d wkt=%s [%s][%s][%s] \n",aStation->call_sign,ts->tm_year,ts->tm_mon,ts->tm_mday,ts->tm_hour,ts->tm_min,ts->tm_sec,wkt,aprs_type,aprs_symbol,record_type);
2226 }
2227 // send query
2228 mysqlreturn = mysql_stmt_execute(statement);
2229 if (mysqlreturn!=0)
2230 {
2231 returnvalue=0;
2232 fprintf(stderr,"%s\n",mysql_stmt_error(statement));
2233 mysql_interpret_error(mysqlreturn,aDbConnection);
2234 }
2235 else
2236 {
2237 returnvalue=1;
2238 }
2239 }
2240 else
2241 {
2242 fprintf(stderr,"Unable to save station to mysql db, Error converting latitude or longitude from xastir coordinates\n");
2243 xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Error converting latitude or longitude from xastir coordinates: %ld,%ld",aStation->coord_lat,aStation->coord_lon);
2244 }
2245 }
2246 else
2247 {
2248 // set call not null error message
2249 fprintf(stderr,"Unable to save station to mysql db, Station call sign was blank or null.\n");
2250 xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Station callsign is required and was blank or null.");
2251 }
2252 } // end of bind check
2253 } // end of parameter count check
2254 }
2255 mysql_stmt_free_result(statement);
2256 mysql_stmt_close(statement);
2257 if (returnvalue==0)
2258 {
2259 pingConnection(aDbConnection);
2260 }
2261
2262 return returnvalue;
2263 }
2264
2265
2266
2267
2268
2269
getAllSimplePositionsMysqlSpatial(Connection * aDbConnection)2270 int getAllSimplePositionsMysqlSpatial(Connection *aDbConnection)
2271 {
2272 int returnvalue = 0;
2273 DataRow *p_new_station;
2274 int station_count = 0; // number of new stations retrieved
2275 char *s_lat[13]; // string latitude
2276 char *s_lon[13]; // string longitude
2277 float lat; // latitude converted from retrieved string
2278 float lon; // longitude converted from retrieved string
2279 unsigned long u_lat;
2280 unsigned long u_long;
2281 int points_this_station;
2282 char feedback[100];
2283 struct tm time;
2284 time_t sec;
2285 int skip; // used in identifying mobile stations
2286 char sql[] = "select station, transmit_time, AsText(position), symbol, overlay, aprstype, origin, record_type, node_path from simpleStationSpatial order by station, transmit_time asc";
2287 char lastcall[MAX_CALLSIGN+1]; //holds last retrieved callsign
2288 int exists; //shortcut to skip db check if currently retrieved callsign equals last retrieved callsign
2289 MYSQL_RES *result;
2290 MYSQL_ROW row;
2291 char empty[MAX_ALTITUDE];
2292 int ok; // to hold mysql_query return value
2293 empty[0]='\0';
2294 ok = mysql_query(&aDbConnection->mhandle,sql);
2295 if (ok==0)
2296 {
2297 result = mysql_use_result(&aDbConnection->mhandle);
2298 if (result!=NULL)
2299 {
2300 xastir_snprintf(feedback,100,"Retrieving MySQL records\n");
2301 stderr_and_statusline(feedback);
2302 // with mysql_use_result each call to mysql_fetch_row retrieves
2303 // a row of data from the server. Mysql_store_result might use
2304 // too much memory in retrieving a large result set all at once.
2305 xastir_snprintf(lastcall,MAX_CALLSIGN+1," ");
2306 points_this_station=0;
2307 while ((row = mysql_fetch_row(result)))
2308 {
2309 // retrieve data from the row
2310 // test to see if this is a valid station
2311 if (row[0]==NULL)
2312 {
2313 // station is null, skip
2314 }
2315 else
2316 {
2317 p_new_station = NULL;
2318 exists = 0;
2319 // Shortcut check to see if station has allready been heard
2320 // works as query is ordered by station.
2321 if (strcmp(lastcall,row[0])==1)
2322 {
2323 exists = 1;
2324 points_this_station++;
2325 }
2326 else
2327 {
2328 if (search_station_name(&p_new_station,row[0],1))
2329 {
2330 exists = 1;
2331 points_this_station++;
2332 }
2333 else
2334 {
2335 points_this_station=1;
2336 }
2337 }
2338 xastir_snprintf(lastcall,MAX_CALLSIGN+1,row[0]);
2339 if (exists==1)
2340 {
2341 // This station is allready in present as a DataRow in the xastir db.
2342 // check to see if this is likely to be a mobile station
2343
2344 // We can't easily identify mobile stations from position position
2345 // becaue of rounding errors, therefore exclude stations that are likely to be fixed.
2346 // _/ = wx
2347 skip = 0;
2348 if ((strcmp(row[3],"_")==0) & (strcmp(row[5],"/")==0))
2349 {
2350 skip = 1; // wx
2351 }
2352 if ((strcmp(row[3],"-")==0) & (strcmp(row[5],"/")==0))
2353 {
2354 skip = 1; // house
2355 }
2356
2357 if (skip==0)
2358 {
2359 // add to track
2360
2361 if (search_station_name(&p_new_station,row[0],1))
2362 {
2363 if (points_this_station<3)
2364 {
2365 //existing station record needs to be added as a trailpoint
2366 (void)store_trail_point(p_new_station, p_new_station->coord_lon, p_new_station->coord_lat, p_new_station->sec_heard, empty, empty, empty, 0);
2367 }
2368 // store this trail point
2369 lat = xastirWKTPointToLatitude(row[2]);
2370 lon = xastirWKTPointToLongitude(row[2]);
2371 if (strlen(row[1]) > 0)
2372 {
2373 strptime(row[1], "%Y-%m-%d %H:%M:%S", &time);
2374 sec = mktime(&time);
2375 //fprintf(stderr,"trailpoint time: %ld [%s]\n", sec, row[1]);
2376 }
2377 if(convert_to_xastir_coordinates( &u_long, &u_lat, lon, lat))
2378 {
2379 (void)store_trail_point(p_new_station, u_long, u_lat, sec, empty, empty, empty, 0);
2380 }
2381 if (p_new_station->sec_heard < sec)
2382 {
2383 // update the station record to this position
2384 if(convert_to_xastir_coordinates(&u_long, &u_lat, lon, lat))
2385 {
2386 p_new_station->coord_lat = u_lat;
2387 p_new_station->coord_lon = u_long;
2388 p_new_station->sec_heard = sec;
2389 }
2390 }
2391 } // search_station_name
2392 } // !skip
2393 }
2394 else
2395 {
2396 // This station isn't in the xastir db.
2397 // Add a datarow using the retrieved station record from the postgis database.
2398 lat = xastirWKTPointToLatitude(row[2]);
2399 lon = xastirWKTPointToLongitude(row[2]);
2400 xastir_snprintf(s_lat,13,"%3.6f",lat);
2401 xastir_snprintf(s_lon,13,"%3.6f",lon);
2402 add_simple_station(p_new_station, row[0], row[6], row[3], row[4], row[5], s_lat, s_lon, row[7], row[8], row[1],(char*)MYSQL_TIMEFORMAT);
2403
2404 station_count++;
2405 }
2406 }
2407 }
2408 }
2409 else
2410 {
2411 // error fetching the result set
2412 fprintf(stderr,"mysql error: %s\n",mysql_error(&aDbConnection->mhandle));
2413 mysql_interpret_error(mysql_errno(&aDbConnection->mhandle),aDbConnection);
2414 }
2415 xastir_snprintf(feedback,100,"Retreived %d new stations from MySQL\n",station_count);
2416 stderr_and_statusline(feedback);
2417 mysql_free_result(result);
2418 }
2419 else
2420 {
2421 // query didn't execute correctly
2422 mysql_interpret_error(ok,aDbConnection);
2423 }
2424
2425 return returnvalue;
2426 }
2427
2428
2429
2430
2431
2432
getAllCadFromGisDbMysql(Connection * aDbConnection)2433 int getAllCadFromGisDbMysql(Connection *aDbConnection)
2434 {
2435 int returnvalue = 0;
2436 int mysqlreturn;
2437 MYSQL *conn = &aDbConnection->mhandle;
2438
2439 return returnvalue;
2440 }
2441
2442
2443
2444
2445
getAllSimplePositionsMysqlSpatialInBoundingBox(Connection * aDbConnection,char * str_e_long,char * str_w_long,char * str_n_lat,char * str_s_lat)2446 int getAllSimplePositionsMysqlSpatialInBoundingBox(Connection *aDbConnection, char* str_e_long, char* str_w_long, char* str_n_lat, char* str_s_lat)
2447 {
2448 int returnvalue = 0;
2449 int mysqlreturn;
2450 MYSQL *conn = &aDbConnection->mhandle;
2451
2452 return returnvalue;
2453
2454 }
2455
2456
2457
2458
2459 /*
2460
2461 // some thoughts on database schema elements
2462
2463 create database xastir;
2464 grant select on xastir to user xastir_user@localhost identified by encrypted password '<password>';
2465
2466 create table version (
2467 version_number int,
2468 compatable_series int
2469 );
2470 grant select on version to xastir_user@localhost
2471 insert into version (version_number) values (XASTIR_SPATIAL_DB_VERSION);
2472 insert into version (version_number) values (XASTIR_SPATIAL_DB_COMPATIBLE_SERIES);
2473
2474 # should be minimum fields needed to populate a DataRow and a related
2475 # APRS_Symbol in xastir
2476 create table simpleStation (
2477 simpleStationId int primary key not null auto_increment
2478 station varchar(MAX_CALLSIGN) not null, # callsign of station, length up to max_callsign
2479 symbol varchar(1), # aprs symbol character
2480 overlay varchar(1), # aprs overlay table character
2481 aprstype varchar(1), # aprs type, required???
2482 transmit_time datetime not null default now(), # transmission time, if available, otherwise storage time
2483 position POINT # position of station or null if latitude and longitude are not available
2484 );
2485
2486
2487
2488 grant select, insert on simpleStation to xastir_user@localhost;
2489
2490
2491 create table datarow (
2492 datarow_id int not null primary key auto_increment,
2493 call_sign varchar(10) not null,
2494 tactical_call_sign varchar() not null default '',
2495 c_aprs_symbol_id int
2496 location POINT,
2497 time_sn int,
2498 sec_heard long,
2499 heard_via_tnc_last_time long,
2500 direct_heard long,
2501 packet_time varchar,
2502 pos_time varchar,
2503 flag int,
2504 pos_amb varchar(1),
2505 error_ellipse_radius int,
2506 lon_precision int,
2507 lat_precision int,
2508 trail_color int,
2509 record_type varchar(1),
2510 data_via varchar(1),
2511 heard_via_tnc_port int,
2512 last_port_heard int,
2513 num_packets int,
2514 altitude varchar([MAX_ALTITUDE]),
2515 speed varchar([MAX_SPEED+1]),
2516 course varchar([MAX_COURSE+1]),
2517 bearing varchar([MAX_COURSE+1]),
2518 NRQ varchar([MAX_COURSE+1]),
2519 power_gain varchar([MAX_POWERGAIN+1]),
2520 signal_gain varchar([MAX_POWERGAIN+1])
2521 );
2522
2523
2524
2525 */
2526
2527 #endif /* HAVE_MYSQL_SPATIAL */
2528
2529 #endif /* HAVE_SPATIAL_DB */
2530
2531
2532
2533
2534
2535
2536 // Layer 3b: DBMS specific db storage code for non spatial databases **********
2537 // Functions supporting queries to specific types of databases that lack
2538 // spatial extensions. Limited to storing points using latitude and longitude
2539 // fields without spatial objects or spatial indexing.
2540 //
2541
2542 #ifdef HAVE_MYSQL
2543 // functions for MySQL database version < 4.1, or MySQL schema objects that don't
2544 // include spatial indicies.
2545 //
2546 //********* Support for MySQL < 4.1 is depreciated *****************************
2547 //********* Expect MySQL support to be limited to MySQL 5+ **********************
2548 //
2549
2550
2551
2552
2553
2554 /* function storeStationSimplePointToDbMysql()
2555 * MySQL implementation of wrapper storeStationSimplePointToGisDb().
2556 * Should be private to db_gis.c
2557 * Should only be called through wrapper function. Do not call directly.
2558 * Returns 0 for failure, 1 for success.
2559 * If failure, stores error message in aDbConnection->errormessage.
2560 */
storeStationSimplePointToDbMysql(Connection * aDbConnection,DataRow * aStation)2561 int storeStationSimplePointToDbMysql(Connection *aDbConnection, DataRow *aStation)
2562 {
2563 int returnvalue = 0; // default return value is failure.
2564 int mysqlreturn = 1; // result of sending mysql query.
2565 char sql[400];
2566 // Next three variables are one character in two bytes plus one character for
2567 // filling by mysql_real_escape_string().
2568 char aprs_symbol[3]; // temporary holding for escaped aprs symbol
2569 char aprs_type[3]; // temporary holding for escaped aprs type
2570 char special_overlay[3]; // temporary holding for escaped overlay
2571 char record_type[3]; // temporary holding for escaped record type
2572 char from[3]; // temporary holding for all of the above length 3 variables
2573 char call_sign[(MAX_CALLSIGN)*2+1]; // temporary holding for escaped callsign
2574 char origin[(MAX_CALLSIGN)*2+1]; // temporary holding for escaped origin
2575 char node_path[(NODE_PATH_SIZE*2)+1]; // temporary holding for escaped node_path_ptr
2576 float longitude;
2577 float latitude;
2578 int ok;
2579 char timestring[100+1];
2580
2581 if (debug_level & 4096)
2582 {
2583 fprintf(stderr,"In storestationsimpletodbmysql()\n");
2584 }
2585
2586 // prepared statements not present below MySQL version 4.1
2587 // details of prepared statement support changed between versions 4.1 and 5.0.
2588 // char [] sql = "insert into simpleStation (call, transmit_time, latitude, longitude) values ('%1','%2','%3','%4'))";
2589 // call is a required element for a simple station
2590 if (aStation!=NULL && aStation->call_sign!=NULL && strlen(aStation->call_sign)>0)
2591 {
2592 // get time in seconds, adjust to datetime
2593 // If my station or another unset sec_heard is
2594 // encountered, use current time instead, use time
2595 // provided if it was invalid.
2596 get_iso_datetime(aStation->sec_heard,timestring,True,False);
2597 // get coord_lat, coord_long in xastir coordinates and convert to decimal degrees
2598 ok = convert_from_xastir_coordinates (&longitude, &latitude, aStation->coord_lon, aStation->coord_lat);
2599 // latitude and longitude are required elements for a simple station record.
2600 if (ok==1)
2601 {
2602 // build insert query with call, time, and position
2603 // handle special cases of null, \ and ' characters in type, symbol, and overlay.
2604 if (aStation->aprs_symbol.aprs_symbol)
2605 {
2606 xastir_snprintf(from,2,"%c",aStation->aprs_symbol.aprs_symbol);
2607 mysql_real_escape_string(&aDbConnection->mhandle,aprs_symbol,from,1);
2608 }
2609 else
2610 {
2611 xastir_snprintf(aprs_symbol,2,"%c",'\0');
2612 }
2613 if (aStation->aprs_symbol.aprs_type)
2614 {
2615 xastir_snprintf(from,2,"%c",aStation->aprs_symbol.aprs_type);
2616 mysql_real_escape_string(&aDbConnection->mhandle,aprs_type,from,1);
2617 }
2618 else
2619 {
2620 xastir_snprintf(aprs_type,2,"%c",'\0');
2621 }
2622 if (aStation->aprs_symbol.special_overlay)
2623 {
2624 xastir_snprintf(from,2,"%c",aStation->aprs_symbol.special_overlay);
2625 mysql_real_escape_string(&aDbConnection->mhandle,special_overlay,from,1);
2626 }
2627 else
2628 {
2629 xastir_snprintf(special_overlay,2,"%c",'\0');
2630 }
2631
2632 // Need to escape call sign - may contain special characters:
2633 // insert into simpleStation (station, symbol, overlay, aprstype, transmit_time, latitude, longitude)
2634 // values ('Fry's','/\0\0',' ','//\0','2007-08-07 21:55:43 -0400','47.496834','-122.198166')
2635 mysql_real_escape_string(&aDbConnection->mhandle,call_sign,(aStation->call_sign),strlen(aStation->call_sign));
2636 // just in case, set a default value for record_type and escape it.
2637 if (aStation->record_type)
2638 {
2639 fprintf(stderr,"record_type: %c\n",aStation->record_type);
2640 xastir_snprintf(from,2,"%c",aStation->record_type);
2641 mysql_real_escape_string(&aDbConnection->mhandle,record_type,from,1);
2642 }
2643 else
2644 {
2645 xastir_snprintf(record_type,2,"%c",NORMAL_APRS);
2646 }
2647
2648 if (strlen(aStation->origin) > 0)
2649 {
2650 mysql_real_escape_string(&aDbConnection->mhandle,origin,(aStation->origin),strlen(aStation->origin));
2651 }
2652 else
2653 {
2654 xastir_snprintf(origin,2,"%c",'\0');
2655 }
2656 if (aStation->node_path_ptr)
2657 {
2658 //mysql_real_escape_string(conn,&node_path,aStation->node_path_ptr,((strlen(aStation->node_path_ptr)*2)+1));
2659 xastir_snprintf(node_path,sizeof(node_path),"%s",aStation->node_path_ptr);
2660 }
2661 else
2662 {
2663 xastir_snprintf(node_path,2,"%c",'\0');
2664 }
2665
2666 xastir_snprintf(sql,sizeof(sql),"insert into simpleStation (station, symbol, overlay, aprstype, transmit_time, latitude, longitude, origin, record_type, node_path) values ('%s','%s','%s','%s','%s','%3.6f','%3.6f','%s','%s','%s')", call_sign, aprs_symbol, special_overlay, aprs_type,timestring,latitude,longitude,origin,record_type,node_path);
2667
2668 if (debug_level & 4096)
2669 {
2670 fprintf(stderr,"MySQL Query:\n%s\n",sql);
2671 }
2672
2673 // send query
2674 mysql_ping(&aDbConnection->mhandle);
2675 mysqlreturn = mysql_real_query(&aDbConnection->mhandle, sql, strlen(sql)+1);
2676 if (mysqlreturn!=0)
2677 {
2678 // get the mysql error message
2679 fprintf(stderr,mysql_error(&aDbConnection->mhandle));
2680 fprintf(stderr,"\n");
2681 mysql_interpret_error(mysqlreturn,aDbConnection);
2682 }
2683 else
2684 {
2685 // insert query was successfull, return value is ok.
2686 returnvalue=1;
2687 }
2688 }
2689 else
2690 {
2691 xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Error converting latitude or longitude from xastir coordinates: %ld,%ld",aStation->coord_lat,aStation->coord_lon);
2692 }
2693 }
2694 else
2695 {
2696 // set call not null error message
2697 xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Station callsign is required and was blank or null.");
2698 }
2699 return returnvalue;
2700 }
2701
2702
2703
2704
2705
2706 /* function testXastirVersionMysql()
2707 * checks the xastir database version number of a connected MySQL database against the
2708 * version range supported by the running copy of xastir.
2709 * @param aDbConnection pointer to a Connection struct describing the connection
2710 * @returns 0 if incompatable, 1 if compatable, -1 on connection failure.
2711 *
2712 * db program
2713 * v cs v cs compatable
2714 * 1 1 1 1 1 identical
2715 * 2 1 1 1 1 database newer than program (added fields, not queried)
2716 * 1 1 2 1 0 program newer than database (added fields, queries fail).
2717 * 3 2 2 1 0 different series
2718 * 2 1 3 2 0 different series
2719 *
2720 * TODO: Need function to test for available schemas with mysql_list_tables()
2721 */
testXastirVersionMysql(Connection * aDbConnection)2722 int testXastirVersionMysql(Connection *aDbConnection)
2723 {
2724 int returnvalue = -1;
2725 MYSQL_RES *result;
2726 MYSQL_ROW row;
2727 int version_number;
2728 int compatible_series;
2729 char sql[] = "select version_number, compatable_series from version order by version_number desc limit 1";
2730 int ok; // to hold mysql_query return value
2731 ok = mysql_query(&aDbConnection->mhandle,sql);
2732 if (ok==0)
2733 {
2734 result = mysql_use_result(&aDbConnection->mhandle);
2735 if (result!=NULL)
2736 {
2737 if ((row = mysql_fetch_row(result)))
2738 {
2739 version_number = atoi((char *)row[0]);
2740 if (version_number == XASTIR_SPATIAL_DB_VERSION)
2741 {
2742 returnvalue = 1;
2743 fprintf(stderr,"Version in schema (%d) is the same as this version of xastir (%d).\n",version_number,XASTIR_SPATIAL_DB_VERSION);
2744 }
2745 else
2746 {
2747 compatible_series = atoi((char *)row[1]);
2748 if (version_number < XASTIR_SPATIAL_DB_VERSION && compatible_series == XASTIR_SPATIAL_DB_COMPATABLE_SERIES)
2749 {
2750 returnvalue = 1;
2751 fprintf(stderr,"Version in schema (%d) is compatible with this version of xastir (%d).\n",version_number,XASTIR_SPATIAL_DB_VERSION);
2752 }
2753 else
2754 {
2755 fprintf(stderr,"Version in schema (%d) is not compatible with this version of xastir (%d).\n",version_number,XASTIR_SPATIAL_DB_VERSION);
2756 xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Version in schema (%d) is not compatible with this version of xastir (%d).",version_number,XASTIR_SPATIAL_DB_VERSION);
2757 fprintf(stderr,"%s",aDbConnection->errormessage);
2758 returnvalue = 0;
2759 }
2760 }
2761 }
2762 else
2763 {
2764 // result returned, but no rows = incompatable
2765 returnvalue = 0;
2766 fprintf(stderr,"Version table doesn't appear to contain any rows.\n");
2767 xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Version table doesn't appear to contain any rows.");
2768 }
2769 }
2770 else
2771 {
2772 fprintf(stderr,"Schema doesn't appear to contain a version table.\n");
2773 xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Schema doesn't appear to contain a version table.");
2774 }
2775 mysql_free_result(result);
2776 }
2777 else
2778 {
2779 fprintf(stderr,"Query failed, Schema doesn't appear to contain a version table.\n");
2780 xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "Query for version table failed.");
2781 }
2782 return returnvalue;
2783 }
2784
2785
2786
2787
2788
2789 /* function storeStationToDbMysql()
2790 */
storeStationToDbMysql(Connection * aDbConnection,DataRow * aStation)2791 int storeStationToDbMysql(Connection *aDbConnection, DataRow *aStation)
2792 {
2793 int returnvalue = 0;
2794 //ioparam *device = aDbConnection->descriptor;
2795 // check type of schema to use (XASTIR simple, full or APRSWorld)
2796 switch (devices[aDbConnection->interface_number].database_schema_type)
2797 {
2798 case XASTIR_SCHEMA_SIMPLE :
2799 returnvalue = storeStationSimplePointToDbMysql(aDbConnection,aStation);
2800 break;
2801 case XASTIR_SCHEMA_APRSWORLD :
2802 break;
2803 case XASTIR_SCHEMA_COMPLEX :
2804 break;
2805 // otherwise error message
2806 }
2807 return returnvalue;
2808 }
2809
2810
2811
2812
2813
2814 /* function getAllSimplePositionsMysql()
2815 * MySQL implementation of getAllSimplePositions for a MySQL database that
2816 * does not include spatial support.
2817 * @param aDbConnection an exastir database connection struct describing
2818 * the connection.
2819 * Returns 0 for failure, 1 for success.
2820 * If failure, stores error message in aDbConnection->errormessage.
2821 */
getAllSimplePositionsMysql(Connection * aDbConnection)2822 int getAllSimplePositionsMysql(Connection *aDbConnection)
2823 {
2824 int returnvalue = 0;
2825 DataRow *p_new_station;
2826 //DataRow *p_time;
2827 int station_count = 0; // number of new stations retrieved
2828 //unsigned long x; // xastir coordinate for longitude
2829 //unsigned long y; // xastir coordinate for latitide
2830 //float lat; // latitude converted from retrieved string
2831 //float lon; // longitude converted from retrieved string
2832 char feedback[100];
2833 //struct tm time;
2834 char sql[] = "select station, transmit_time, latitude, longitude, symbol, overlay, aprstype, origin, record_type, node_path from simpleStation order by station, transmit_time";
2835 MYSQL_RES *result;
2836 MYSQL_ROW row;
2837 int ok; // to hold mysql_query return value
2838 ok = mysql_query(&aDbConnection->mhandle,sql);
2839 if (ok==0)
2840 {
2841 result = mysql_use_result(&aDbConnection->mhandle);
2842 if (result!=NULL)
2843 {
2844 xastir_snprintf(feedback,100,"Retrieving MySQL records\n");
2845 stderr_and_statusline(feedback);
2846 // with mysql_use_result each call to mysql_fetch_row retrieves
2847 // a row of data from the server. Mysql_store_result might use
2848 // too much memory in retrieving a large result set all at once.
2849 while ((row = mysql_fetch_row(result)))
2850 {
2851 // retrieve data from the row
2852 // test to see if this is a valid station
2853 if (row[0]==NULL)
2854 {
2855 // station is null, skip
2856 }
2857 else
2858 {
2859 p_new_station = NULL;
2860 if (search_station_name(&p_new_station,row[0],1))
2861 {
2862 // This station is allready in present as a DataRow in the xastir db.
2863 // Add data to the station's track.
2864 }
2865 else
2866 {
2867 // This station isn't in the xastir db.
2868 // Add a datarow using the retrieved station record from the postgis database.
2869 add_simple_station(p_new_station, row[0], row[7], row[4], row[5], row[6], row[2], row[3], row[8], row[9], row[1],(char*)MYSQL_TIMEFORMAT);
2870
2871 station_count++;
2872 }
2873 }
2874 }
2875 }
2876 else
2877 {
2878 // error fetching the result set
2879 fprintf(stderr,"mysql error: %s\n",mysql_error(&aDbConnection->mhandle));
2880 mysql_interpret_error(mysql_errno(&aDbConnection->mhandle),aDbConnection);
2881 }
2882 xastir_snprintf(feedback,100,"Retreived %d new stations from MySQL\n",station_count);
2883 stderr_and_statusline(feedback);
2884 mysql_free_result(result);
2885 }
2886 else
2887 {
2888 // query didn't execute correctly
2889 mysql_interpret_error(ok,aDbConnection);
2890 }
2891 return returnvalue;
2892 }
2893
2894
getAllSimplePositionsMysqlInBoundingBox(Connection * aDbConnection,char * str_e_long,char * str_w_long,char * str_n_lat,char * str_s_lat)2895 int getAllSimplePositionsMysqlInBoundingBox(Connection *aDbConnection, char *str_e_long, char *str_w_long, char *str_n_lat, char *str_s_lat)
2896 {
2897 int returnvalue = 0;
2898
2899 return returnvalue;
2900 }
2901
2902
2903
2904 /* function mysql_interpret_error()
2905 * given a mysql error code and an xastir connection, sets an appropriate
2906 * error message in the errormessage field of the connection. Interprets
2907 * numeric error codes returned by mysql functions.
2908 * @param errorcode A result returned by a mysql function that can be
2909 * interpreted as an error code.
2910 * @param aDbConnection an xastir database connection struct describing the
2911 * connection and its current state.
2912 * Note - it is possible to give this function a connection on which an
2913 * error has not occured along with an error code. This function does
2914 * not check the connection or assess whether an error actually occured
2915 * on it or not, it simply interprets an error code and writes the
2916 * interpretation into the connection that was passed to it.
2917 */
mysql_interpret_error(int errorcode,Connection * aDbConnection)2918 void mysql_interpret_error(int errorcode, Connection *aDbConnection)
2919 {
2920 fprintf(stderr,"Error communicating with MySQL database. Error code=%d\n",errorcode);
2921 switch (errorcode)
2922 {
2923 case CR_OUT_OF_MEMORY :
2924 // insufficient memory for query
2925 xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "MySQL: Out of Memory");
2926 // notify the connection status listener
2927 break;
2928 // mysql_query errors
2929 case CR_COMMANDS_OUT_OF_SYNC :
2930 // commands in improper order
2931 xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "MySQL: Commands out of sync");
2932 break;
2933 case CR_SERVER_GONE_ERROR :
2934 // mysql server has gone away
2935 xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "MySQL: Connection to server lost");
2936 // notify the connection status listener
2937 break;
2938 case CR_SERVER_LOST :
2939 // server connection was lost during query
2940 xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "MySQL: Connection to server lost during query");
2941 // notify the connection status listener
2942 break;
2943 case CR_UNKNOWN_ERROR :
2944 xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "MySQL: Unknown Error");
2945 break;
2946 default:
2947 xastir_snprintf(aDbConnection->errormessage, MAX_CONNECTION_ERROR_MESSAGE, "MySQL: Unrecognized error Code [%d]", errorcode);
2948 }
2949 fprintf(stderr,"%s\n",aDbConnection->errormessage);
2950 }
2951
2952 #endif /* HAVE_MYSQL*/
2953
2954 // add code for a lightweight database here
2955
2956
2957
2958
2959
2960
2961
2962 #endif /* HAVE_DB*/
2963
2964 // Functions related to GIS, but not database specific ************************
2965
2966
2967
2968
2969 /* Function xastirCoordToLatLongPostgresPoint
2970 * converts a point in xastir coordinates to a native postgres representation
2971 * of a point using latitude and longitude in decimal degrees in the WGS84
2972 * projection EPSG:4326. Format is similar to WKT, but without leading POINT.
2973 * @param x longitude in xastir coordinates = decimal 100ths of a second.
2974 * @param y latitude in xastir coordinates = decimal 100ths of a second.
2975 * @param pointer to a char[ at least 24] string to hold point representation.
2976 * returns 1 on success, 0 on failure.
2977 */
xastirCoordToLatLongPostgresPoint(long x,long y,char * wkt)2978 int xastirCoordToLatLongPostgresPoint(long x, long y, char *wkt)
2979 {
2980 // 1 xastir coordinate = 1/100 of a second
2981 // 100*60*60 xastir coordinates (=360000 xastir coordinates) = 1 degree
2982 // 360000 xastir coordinates = 1 degree
2983 // conversion to string decimal degrees handled by utility fuctions
2984 int returnvalue = 0; // defaults to failure
2985 float latitude;
2986 float longitude;
2987 int ok;
2988 ok = convert_from_xastir_coordinates (&longitude,&latitude, x, y);
2989 if (ok>0)
2990 {
2991 xastir_snprintf(wkt, MAX_WKT, "(%3.6f, %3.6f)", latitude, longitude);
2992 returnvalue = 1;
2993 }
2994 return returnvalue;
2995 }
2996
2997
2998
2999
3000
3001
3002 /* Function xastirCoordToLatLongWKT
3003 * converts a point in xastir coordinates to a well known text string (WKT)
3004 * representation of a point using latitude and longitude in decimal degrees
3005 * in the WGS84 projection EPSG:4326
3006 * @param x longitude in xastir coordinates = decimal 100ths of a second.
3007 * @param y latitude in xastir coordinates = decimal 100ths of a second.
3008 * @param pointer to a char[29] string to hold well known text representation.
3009 * returns 1 on success, 0 on failure.
3010 */
xastirCoordToLatLongWKT(long x,long y,char * wkt)3011 int xastirCoordToLatLongWKT(long x, long y, char *wkt)
3012 {
3013 // 1 xastir coordinate = 1/100 of a second
3014 // 100*60*60 xastir coordinates (=360000 xastir coordinates) = 1 degree
3015 // 360000 xastir coordinates = 1 degree
3016 // conversion to string decimal degrees handled by utility fuctions
3017 int returnvalue = 0; // defaults to failure
3018 float latitude;
3019 float longitude;
3020 int ok;
3021 ok = convert_from_xastir_coordinates (&longitude,&latitude, x, y);
3022 if (ok>0)
3023 {
3024 xastir_snprintf(wkt, MAX_WKT, "POINT(%3.6f %3.6f)", longitude, latitude);
3025 returnvalue = 1;
3026 }
3027 return returnvalue;
3028 }
3029
3030
3031
3032
3033
xastirWKTPointToLongitude(char * wkt)3034 float xastirWKTPointToLongitude(char *wkt)
3035 {
3036 float returnvalue = 0.0;
3037 char temp[MAX_WKT];
3038 char *space = NULL;
3039 int x;
3040 if (wkt[0]=='P' && wkt[1]=='O' && wkt[2]=='I' && wkt[3]=='N' && wkt[4]=='T' && wkt[5]=='(')
3041 {
3042 // this is a point
3043 xastir_snprintf(temp, MAX_WKT, "%s", wkt);
3044 // truncate at the space
3045 space = strchr(temp,' ');
3046 if (space != NULL)
3047 {
3048 *space = '\0';
3049 }
3050 // remove the leading "POINT("
3051 for (x=0; x<6; x++)
3052 {
3053 temp[x]=' ';
3054 }
3055 returnvalue = atof(temp);
3056 }
3057 return returnvalue;
3058 }
3059
3060
3061
3062
3063
xastirWKTPointToLatitude(char * wkt)3064 float xastirWKTPointToLatitude(char *wkt)
3065 {
3066 float returnvalue = 0.0;
3067 char temp[MAX_WKT];
3068 char *paren = NULL;
3069 int x;
3070 if (wkt[0]=='P' && wkt[1]=='O' && wkt[2]=='I' && wkt[3]=='N' && wkt[4]=='T' && wkt[5]=='(')
3071 {
3072 // this is a point
3073 xastir_snprintf(temp, MAX_WKT, "%s", wkt);
3074 // truncate at the trailing parenthesis
3075 paren = strchr(temp,')');
3076 if (paren != NULL)
3077 {
3078 *paren = '\0';
3079 }
3080 // convert all leading characters up to the space to spaces.
3081 for (x=0; x<(int)(strlen(temp)); x++)
3082 {
3083 if (temp[x]==' ')
3084 {
3085 x = (int)(strlen(temp));
3086 }
3087 else
3088 {
3089 temp[x] = ' ';
3090 }
3091 }
3092 returnvalue = atof(temp);
3093 }
3094 return returnvalue;
3095
3096 }
3097
3098