Initial import of r35+comments of the bzr tree of gpx-import
authorDaniel Silverstone <dsilvers@svn.openstreetmap.org>
Fri, 7 Nov 2008 10:57:53 +0000 (10:57 +0000)
committerDaniel Silverstone <dsilvers@svn.openstreetmap.org>
Fri, 7 Nov 2008 10:57:53 +0000 (10:57 +0000)
25 files changed:
BUILD [new file with mode: 0644]
LICENSE [new file with mode: 0644]
settings.sh [new file with mode: 0755]
src/Makefile [new file with mode: 0644]
src/db.c [new file with mode: 0644]
src/db.h [new file with mode: 0644]
src/filename.c [new file with mode: 0644]
src/filename.h [new file with mode: 0644]
src/gpx.c [new file with mode: 0644]
src/gpx.h [new file with mode: 0644]
src/image.c [new file with mode: 0644]
src/image.h [new file with mode: 0644]
src/interpolate.c [new file with mode: 0644]
src/interpolate.h [new file with mode: 0644]
src/log.c [new file with mode: 0644]
src/log.h [new file with mode: 0644]
src/main.c [new file with mode: 0644]
src/mercator.c [new file with mode: 0644]
src/mercator.h [new file with mode: 0644]
src/mysql.c [new file with mode: 0644]
src/quadtile.c [new file with mode: 0644]
src/quadtile.h [new file with mode: 0644]
templates/README [new file with mode: 0644]
templates/import-bad.eml [new file with mode: 0644]
templates/import-ok.eml [new file with mode: 0644]

diff --git a/BUILD b/BUILD
new file mode 100644 (file)
index 0000000..71c825a
--- /dev/null
+++ b/BUILD
@@ -0,0 +1,13 @@
+To build gpx-import:
+
+You will need a C compiler, and the following Debian/Ubuntu packages:
+  zlib1g-dev libbz2-dev libarchive-dev libexpat1-dev libgd2-noxpm-dev
+
+then run
+  make -C src
+
+To run gpx-import, ./settings.sh src/gpx-import
+
+Enjoy,
+
+Daniel.
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..52bf1ab
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,356 @@
+    OpenStreetMap GPX Import Daemon
+    Copyright (C) 2008  Daniel Silverstone <dsilvers@digital-scurf.org>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; version 2 of the License *ONLY*.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+
+The full terms of the GNU GPL follow:
+
+
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) 19yy  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) 19yy name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/settings.sh b/settings.sh
new file mode 100755 (executable)
index 0000000..d9e94e4
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+# This script configures the environment to use the OSM db
+# so that the gpx-import program can find it.
+
+setting () {
+  S_N=GPX_$1
+  shift
+  eval "${S_N}='$*'"
+  export ${S_N}
+}
+
+# General settings
+setting SLEEP_TIME 1
+
+# Paths (can be relative from invocation path if appropriate)
+setting PATH_TRACES /tmp/osm/traces
+setting PATH_IMAGES /tmp/osm/images
+setting PATH_TEMPLATES templates/
+
+# MySQL connection
+setting MYSQL_HOST localhost
+setting MYSQL_USER openstreetmap
+setting MYSQL_DB openstreetmap
+setting MYSQL_PASS openstreetmap
+
+# Optional debug statements
+#setting INTERPOLATE_STDOUT 1
+
+# Run the commandline
+
+exec "$@"
+
diff --git a/src/Makefile b/src/Makefile
new file mode 100644 (file)
index 0000000..c42c176
--- /dev/null
@@ -0,0 +1,42 @@
+# gpx-import/src/Makefile
+#
+# GPX Import tool for OpenStreetMap
+#
+# Copyright 2008 Daniel Silverstone <dsilvers@digital-scurf.org>
+#
+
+all: gpx-import
+
+DB := mysql
+
+OPT := -O0
+CFLAGS := $(OPT) -g -Wall -std=c99 -D_POSIX_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE -include log.h
+
+# Comment this out to get the DEBUG log lines
+CFLAGS += -DNDEBUG
+
+# Get dep files
+CFLAGS += -MD
+
+CFLAGS += $(shell gdlib-config --cflags)
+LDFLAGS += $(shell gdlib-config --libs) -lgd 
+
+ifeq ($(DB),mysql)
+CFLAGS += $(shell mysql_config --cflags)
+LDFLAGS += $(shell mysql_config --libs)
+endif
+
+MAINOBJS := main.o gpx.o mercator.o image.o log.o db.o filename.o interpolate.o quadtile.o
+
+ALLOBJS := $(DB).o $(MAINOBJS)
+
+gpx-import: $(ALLOBJS)
+       gcc $(LDFLAGS) -o $@ $^ -lexpat -larchive -lz -lbz2
+
+clean:
+       $(RM) *.o gpx-import *.d
+
+distclean: clean
+       $(RM) *~
+
+-include $(patsubst %.o,%.d,$(ALLOBJS))
diff --git a/src/db.c b/src/db.c
new file mode 100644 (file)
index 0000000..1cd22f9
--- /dev/null
+++ b/src/db.c
@@ -0,0 +1,57 @@
+/* gpx-import/src/db.h
+ *
+ * Database interface layer
+ *
+ * Copyright 2008 Daniel Silverstone <dsilvers@digital-scurf.org>
+ *
+ * Written for OpenStreetMap
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2 of the License
+ * only.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "db.h"
+
+void
+db_free_job(DBJob *job)
+{
+  if (job == NULL)
+    return;
+  
+  if (job->gpx != NULL) {
+    gpx_free(job->gpx);
+  }
+  
+  free(job->title);
+  free(job->description);
+  free(job->tags);
+  free(job->email);
+  free(job->error);
+  
+  free(job);
+}
+
+void
+db_error(DBJob *job, const char *fmt, ...)
+{
+  va_list va;
+  int sz;
+  
+  va_start(va, fmt);
+  sz = vsnprintf(NULL, 0, fmt, va);
+  job->error = malloc(sz + 1);
+  vsnprintf(job->error, sz + 1, fmt, va);
+  va_end(va);
+}
+
diff --git a/src/db.h b/src/db.h
new file mode 100644 (file)
index 0000000..f79a48a
--- /dev/null
+++ b/src/db.h
@@ -0,0 +1,53 @@
+/* gpx-import/src/db.h
+ *
+ * Database interface layer
+ *
+ * Copyright 2008 Daniel Silverstone <dsilvers@digital-scurf.org>
+ *
+ * Written for OpenStreetMap
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2 of the License
+ * only.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#ifndef GPX_IMPORT_DB_H
+#define GPX_IMPORT_DB_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "gpx.h"
+
+typedef struct {
+  GPX *gpx;
+  int64_t gpx_id;
+  char *title;
+  char *description;
+  char *tags;
+  char *email;
+  
+  /* If this is non-NULL then an error has occurred */
+  char *error;
+} DBJob;
+
+/* All these functions are in the database backend files. */
+extern bool db_connect(void);
+extern DBJob *db_find_work(int minage);
+extern bool db_insert_gpx(DBJob *job);
+extern int64_t db_find_invisible(void);
+extern bool db_destroy_trace(int64_t jobnr);
+extern void db_disconnect(void);
+
+/* These are implemented in db.c rather than in any of the backends.
+ */
+extern void db_free_job(DBJob *job);
+extern void db_error(DBJob *job, const char *fmt, ...);
+
+#endif /* GPX_IMPORT_DB_H */
diff --git a/src/filename.c b/src/filename.c
new file mode 100644 (file)
index 0000000..0bb54ee
--- /dev/null
@@ -0,0 +1,41 @@
+/* gpx-import/src/filename.c
+ *
+ * GPX importer, filename generator
+ *
+ * Copyright Daniel Silverstone <dsilvers@digital-scurf.org>
+ *
+ * Written for the OpenStreetMap project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2 of the License
+ * only.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <stdio.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#include "filename.h"
+
+char *
+make_filename(const char *base, int64_t nr, const char *suffix)
+{
+  static char namebuffer[PATH_MAX];
+  char *real_base = getenv(base);
+  
+  if (real_base == NULL) {
+    WARN("Unable to find base from: %s", base);
+    real_base = ".";
+  }
+  
+  snprintf(namebuffer, PATH_MAX, "%s/%ld%s", real_base, nr, suffix);
+  
+  return namebuffer;
+}
+
diff --git a/src/filename.h b/src/filename.h
new file mode 100644 (file)
index 0000000..a711047
--- /dev/null
@@ -0,0 +1,28 @@
+/* gpx-import/src/filename.h
+ *
+ * GPX importer, filename generator
+ *
+ * Copyright Daniel Silverstone <dsilvers@digital-scurf.org>
+ *
+ * Written for the OpenStreetMap project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2 of the License
+ * only.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#ifndef GPX_FILENAME_H
+#define GPX_FILENAME_H
+
+#include <stdint.h>
+
+/* memory buffer is current shared, copy if you need to retain. */
+extern char *make_filename(const char *base, int64_t nr, const char *suffix);
+
+#endif /* GPX_FILENAME_H */
diff --git a/src/gpx.c b/src/gpx.c
new file mode 100644 (file)
index 0000000..efc208c
--- /dev/null
+++ b/src/gpx.c
@@ -0,0 +1,657 @@
+/* gpx-import/src/gpx.c
+ *
+ * GPX load and memory management
+ *
+ * Copyright Daniel Silverstone <dsilvers@digital-scurf.org>
+ *
+ * Written for the OpenStreetMap project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2 of the License
+ * only.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <sys/types.h>
+#include <expat.h>
+#include <stdlib.h>
+#include <alloca.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <string.h>
+#include <strings.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <time.h>
+
+#include <archive.h>
+#include <archive_entry.h>
+
+#include <zlib.h>
+#include <bzlib.h>
+
+#include "gpx.h"
+
+#define GPX_BUFLEN 4096
+#define ERR_BUFFER_SIZE 1024
+
+#define PARSE_ERROR(ctx,F...)                           \
+  do {                                                  \
+    if (ctx->err != NULL && *(ctx->err) == NULL)        \
+      gpx_record_error(ctx, F);                         \
+  } while (0)
+
+void
+gpx_free(GPX *gpx)
+{
+  GPXTrackPoint *pt = gpx->points, *ptnext;
+  while (pt != NULL) {
+    ptnext = pt->next;
+    free(pt->timestamp);
+    free(pt);
+    pt = ptnext;
+  }
+  free(gpx);
+}
+
+typedef enum {
+  UNKNOWN,
+  TRACKPOINT,
+  ELEVATION,
+  TIMESTAMP,
+} GPXParseState;
+
+static const char *
+gpx_state_name(GPXParseState p)
+{
+  switch (p) {
+  case UNKNOWN:
+    return "UNKNOWN";
+  case TRACKPOINT:
+    return "TRACKPOINT";
+  case ELEVATION:
+    return "ELEVATION";
+  case TIMESTAMP:
+    return "TIMESTAMP";
+  }
+  return "INVALID";
+}
+
+typedef struct {
+  XML_Parser p;
+  GPX *gpx;
+  GPXTrackPoint *point;
+  GPXTrackPoint *lastpoint;
+  uint32_t curseg;
+  char *accumulator;
+  int accumulator_size;
+  GPXParseState state;
+  bool got_lat, got_long, got_ele, got_time;
+  char **err;
+  const char *subfile;
+} GPXParseContext;
+
+static void
+gpx_record_error(GPXParseContext *ctx, char *fmt, ...)
+{
+  va_list va;
+  int sz;
+  char msg[ERR_BUFFER_SIZE];
+  
+  va_start(va, fmt);
+  sz = vsnprintf(msg, ERR_BUFFER_SIZE, fmt, va);
+  va_end(va);
+  
+  *(ctx->err) = calloc(2, ERR_BUFFER_SIZE);
+  snprintf(*(ctx->err), 2 * ERR_BUFFER_SIZE, "%s%s%s%s\n  XML parser at line %ld column %ld",
+           (ctx->subfile == NULL) ? "" : "In file ",
+           (ctx->subfile == NULL) ? "" : ctx->subfile,
+           (ctx->subfile == NULL) ? "" : " inside your upload:\n  ",
+           msg,
+           XML_GetCurrentLineNumber(ctx->p),
+           XML_GetCurrentColumnNumber(ctx->p));
+}
+
+static GPXCoord
+gpx_parse_coord(const XML_Char *str)
+{
+  GPXCoord ret = 0;
+  bool is_neg = false;
+  bool is_afterdot = false;
+  int afterdot = 0;
+  
+  if (*str == '-') {
+    is_neg = true;
+    str++;
+  }
+  while (*str != '\0' && afterdot < 9) {
+    if (*str == '.')
+      is_afterdot = true;
+    if (*str != '.') {
+      ret *= 10;
+      ret += (*str - '0');
+      if (is_afterdot == true)
+        afterdot++;
+    }
+    str++;
+  }
+  
+  while (afterdot < 9) {
+    afterdot++;
+    ret *= 10;
+  }
+  
+  return (is_neg ? -ret : ret);
+}
+
+static void
+gpx_clear_accumulator(GPXParseContext *ctx)
+{
+  if (ctx->accumulator != NULL)
+    free(ctx->accumulator);
+  ctx->accumulator = NULL;
+  ctx->accumulator_size = 0;
+}
+
+#define REQUIRE_STATE(S)                                                \
+  if (ctx->state != (S)) {                                              \
+    PARSE_ERROR(ctx, "Expected state %s, got state %s", gpx_state_name(S), gpx_state_name(ctx->state)); \
+    XML_StopParser(ctx->p, 0);                                          \
+    return;                                                             \
+  }
+
+static void
+gpx_handle_start_element(void *_ctx, const XML_Char *name, const XML_Char **atts)
+{
+  GPXParseContext *ctx = (GPXParseContext *)(_ctx);
+  if (strcmp(name, "trkpt") == 0) {
+    REQUIRE_STATE(UNKNOWN);
+    ctx->state = TRACKPOINT;
+    ctx->point = calloc(1, sizeof(GPXTrackPoint));
+    ctx->point->segment = ctx->curseg;
+    ctx->got_lat = ctx->got_long = ctx->got_ele = ctx->got_time = false;
+    while (*atts != NULL) {
+      if (strcmp(*atts, "lat") == 0) {
+        atts++;
+        ctx->point->latitude = gpx_parse_coord(*atts++);
+        ctx->got_lat = true;
+      } else if (strcmp(*atts, "lon") == 0) {
+        atts++;
+        ctx->point->longitude = gpx_parse_coord(*atts++);
+        ctx->got_long = true;
+      } else {
+        atts += 2; /* skip tag + value */
+      }
+    }
+  } else if (strcmp(name, "ele") == 0) {
+    REQUIRE_STATE(TRACKPOINT);
+    ctx->state = ELEVATION;
+    gpx_clear_accumulator(ctx);
+  } else if (strcmp(name, "time") == 0) {
+    if (ctx->state == TRACKPOINT) {
+      ctx->state = TIMESTAMP;
+      gpx_clear_accumulator(ctx);
+    }
+  }
+}
+
+#ifndef MIN
+#define MIN(a,b) (((a) < (b)) ? (a) : (b))
+#endif
+
+#ifndef MAX
+#define MAX(a,b) (((a) > (b)) ? (a) : (b))
+#endif
+
+static void
+gpx_update_minmax(GPX *gpx, GPXTrackPoint *point)
+{
+  gpx->minlatitude = MIN(gpx->minlatitude, point->latitude);
+  gpx->minlongitude = MIN(gpx->minlongitude, point->longitude);
+  gpx->maxlatitude = MAX(gpx->maxlatitude, point->latitude);
+  gpx->maxlongitude = MAX(gpx->maxlongitude, point->longitude);
+}
+
+static void
+gpx_handle_end_element(void *_ctx, const XML_Char *name)
+{
+  GPXParseContext *ctx = (GPXParseContext *)(_ctx);
+  if (strcmp(name, "trkpt") == 0) {
+    REQUIRE_STATE(TRACKPOINT);
+    /* Remove the || true if elevation is mandatory. */
+    if (ctx->got_time == false)
+      ctx->gpx->missed_time++;
+    
+    if ((ctx->got_lat == false) ||
+        (ctx->point->latitude < -90000000000) ||
+        (ctx->point->latitude > 90000000000))
+      ctx->gpx->bad_lat++;
+    
+    if ((ctx->got_long == false) ||
+        (ctx->point->longitude < -180000000000) ||
+        (ctx->point->longitude > 180000000000))
+      ctx->gpx->bad_long++;
+    
+    if ((ctx->got_lat && ctx->got_long && (ctx->got_ele || true) && ctx->got_time) &&
+        (ctx->point != NULL) && (ctx->point->longitude >= -180000000000) &&
+        (ctx->point->longitude <= 180000000000) &&
+        (ctx->point->latitude >= -90000000000) &&
+        (ctx->point->latitude <= 90000000000)) {
+      gpx_update_minmax(ctx->gpx, ctx->point);
+      
+      if (ctx->lastpoint == NULL) {
+        INFO("Attaching first point");
+        ctx->gpx->firstlatitude = ctx->point->latitude;
+        ctx->gpx->firstlongitude = ctx->point->longitude;
+        ctx->lastpoint = ctx->point;
+        ctx->gpx->points = ctx->point;
+        ctx->point = NULL;
+      } else {
+        ctx->lastpoint->next = ctx->point;
+        ctx->lastpoint = ctx->point;
+        ctx->point = NULL;
+      }
+      
+      ctx->gpx->goodpoints++;
+      
+    } else {
+      ctx->gpx->badpoints++;
+      if (ctx->point->timestamp != NULL)
+        free(ctx->point->timestamp);
+      free(ctx->point);
+      ctx->point = NULL;
+    }
+    ctx->state = UNKNOWN;
+  } else if (strcmp(name, "ele") == 0) {
+    REQUIRE_STATE(ELEVATION);
+    ctx->point->elevation = strtof(ctx->accumulator, NULL);
+    ctx->state = TRACKPOINT;
+    ctx->got_ele = true;
+  } else if (strcmp(name, "time") == 0) {
+    char *pnull = NULL;
+    struct tm ignored;
+    if (ctx->state == UNKNOWN)
+      return;
+    REQUIRE_STATE(TIMESTAMP);
+    pnull = strptime(ctx->accumulator ? ctx->accumulator : "",
+                     "%Y-%m-%dT%H:%M:%S",
+                     &ignored);
+    if (pnull != NULL && (*pnull == '\0' || *pnull == '.' || *pnull == 'Z')) {
+      ctx->point->timestamp = ctx->accumulator;
+      ctx->accumulator = NULL;
+      ctx->got_time = true;
+    }
+    ctx->state = TRACKPOINT;
+  } else if (strcmp(name, "trkseg") == 0) {
+    REQUIRE_STATE(UNKNOWN);
+    ctx->curseg++;
+  }
+}
+
+void
+gpx_handle_string_data(void *_ctx, const XML_Char *str, int len)
+{
+  GPXParseContext *ctx = (GPXParseContext *)(_ctx);
+  
+  if ((ctx->state != ELEVATION) &&
+      (ctx->state != TIMESTAMP))
+    /* We only accumulate during elevation or timestamp */
+    return;
+  
+  if (ctx->accumulator == NULL) {
+    ctx->accumulator = malloc(len + 1);
+    memcpy(ctx->accumulator, str, len);
+    ctx->accumulator_size = len + 1;
+  } else {
+    ctx->accumulator = realloc(ctx->accumulator, ctx->accumulator_size + len);
+    memcpy(ctx->accumulator + ctx->accumulator_size - 1, str, len);
+    ctx->accumulator_size += len;
+  }
+  
+  ctx->accumulator[ctx->accumulator_size - 1] = '\0';
+}
+
+static bool
+gpx_create_parser(GPXParseContext *ctx)
+{
+  ctx->p = XML_ParserCreate(NULL);
+  
+  if (ctx->p == NULL)
+    return false;
+  
+  XML_SetElementHandler(ctx->p, gpx_handle_start_element, gpx_handle_end_element);
+  XML_SetDefaultHandler(ctx->p, gpx_handle_string_data);
+  XML_SetUserData(ctx->p, ctx);
+  
+  return true;
+}
+
+static bool
+gpx_parse_buffer(GPXParseContext *ctx, const char *buffer, ssize_t buflen)
+{
+  if (XML_Parse(ctx->p, buffer, buflen, XML_FALSE) != XML_STATUS_OK) {
+    if (*(ctx->err) == NULL) {
+      PARSE_ERROR(ctx, "Generic XML parse error");
+    }
+    return false;
+  }
+  return true;
+}
+
+static void
+gpx_free_parser(GPXParseContext *ctx)
+{
+  if (ctx->p != NULL)
+    XML_ParserFree(ctx->p);
+  ctx->p = NULL;
+}
+
+static void
+gpx_abort_context(GPXParseContext *ctx)
+{
+  gpx_free(ctx->gpx);
+  if (ctx->point != NULL) {
+    if (ctx->point->timestamp)
+      free(ctx->point->timestamp);
+    free(ctx->point);
+  }
+  if (ctx->accumulator != NULL)
+    free(ctx->accumulator);
+  gpx_free_parser(ctx);
+}
+
+static bool
+gpx_parse_plain_file(GPXParseContext *ctx, const char *gpxfile)
+{
+  char *buffer = alloca(GPX_BUFLEN);
+  ssize_t bufread;
+  int fd = open(gpxfile, O_RDONLY);
+  
+  if (fd == -1) {
+    PARSE_ERROR(ctx, "Error opening file %s: %s", gpxfile, strerror(errno));
+    return false;
+  }
+  
+  if (gpx_create_parser(ctx) == false) {
+    PARSE_ERROR(ctx, "Unable to create XML parser");
+    close(fd);
+    return false;
+  }
+  
+  while ((bufread = read(fd, buffer, GPX_BUFLEN)) > 0) {
+    if (gpx_parse_buffer(ctx, buffer, bufread) == false) {
+      close(fd);
+      return false;
+    }
+  }
+  
+  close(fd);
+  
+  gpx_free_parser(ctx);
+  
+  return true;
+}
+
+static bool
+gpx_parse_archive(GPXParseContext *ctx, const char *gpxfile)
+{
+  struct archive *arch;
+  struct archive_entry *ent;
+  bool okay = false;
+  int64_t sublen;
+  char *buffer = alloca(GPX_BUFLEN);
+  int ret;
+  
+  arch = archive_read_new();
+  if (arch == NULL) {
+    return false;
+  }
+  
+  archive_read_support_compression_gzip(arch);
+  archive_read_support_compression_bzip2(arch);
+  archive_read_support_format_all(arch);
+  
+  if (archive_read_open_filename(arch, gpxfile, 1) < ARCHIVE_OK) {
+    goto out;
+  }
+  
+  while ((ret = archive_read_next_header(arch, &ent)) == ARCHIVE_OK) {
+    ctx->subfile = archive_entry_pathname(ent);
+    sublen = archive_entry_size(ent);
+    if (sublen > 0) {
+      INFO("Considering sub-entry %s in job", ctx->subfile);
+      /* There's data, let's try and parse it */
+      gpx_create_parser(ctx);
+      while (sublen > 0) {
+        if (archive_read_data(arch, buffer, MIN(GPX_BUFLEN, sublen)) < ARCHIVE_OK) {
+          PARSE_ERROR(ctx, "Unable to read data from archive.");
+          goto out;
+        }
+        if (gpx_parse_buffer(ctx, buffer, MIN(GPX_BUFLEN, sublen)) == false) {
+          goto out;
+        }
+        sublen -= MIN(GPX_BUFLEN, sublen);
+      }
+      gpx_free_parser(ctx);
+    }
+    ctx->subfile = NULL;
+  }
+  
+  
+  if ((ret == ARCHIVE_EOF) && ctx->gpx->goodpoints > 0)
+    okay = true;
+  
+  out:
+  archive_read_close(arch);
+  archive_read_finish(arch);
+  return okay;
+}
+
+static char gzip_magic[] = { 0x1f, 0x8b, 0x08 };
+static char bzip2_magic[] = { 0x42, 0x5a, 0x68, 0x39, 0x31 };
+
+static bool
+gpx_try_gzip(GPXParseContext *ctx, const char *gpxfile)
+{
+  int fd = open(gpxfile, O_RDONLY);
+  gzFile *f;
+  char *buffer = alloca(GPX_BUFLEN);
+  ssize_t bufread;
+  
+  if (fd == -1) {
+    PARSE_ERROR(ctx, "Unable to open %s (errno=%s)", gpxfile, strerror(errno));
+    return false;
+  }
+  
+  if (read(fd, buffer, sizeof(gzip_magic)) != sizeof(gzip_magic)) {
+    PARSE_ERROR(ctx, "Unable to read data from %s (errno=%s)", gpxfile, strerror(errno));
+    close(fd);
+    return false;
+  }
+  
+  if (memcmp(buffer, gzip_magic, sizeof(gzip_magic)) != 0) {
+    close(fd);
+    return false;
+  }
+  
+  lseek(fd, 0, SEEK_SET);
+  
+  f = gzdopen(fd, "rb"); /* Takes over the FD */
+  
+  INFO("Detected gzip encoded data");
+  
+  if (gpx_create_parser(ctx) == false) {
+    PARSE_ERROR(ctx, "Unable to create XML parser");
+    gzclose(f);
+    return false;
+  }
+  
+  while ((bufread = gzread(f, buffer, GPX_BUFLEN)) > 0) {
+    if (gpx_parse_buffer(ctx, buffer, bufread) == false) {
+      gzclose(f);
+      return false;
+    }
+  }
+  
+  gzclose(f);
+  
+  gpx_free_parser(ctx);
+  
+  return true;
+}
+
+static bool
+gpx_try_bzip2(GPXParseContext *ctx, const char *gpxfile)
+{
+  int fd = open(gpxfile, O_RDONLY);
+  BZFILE *f;
+  char *buffer = alloca(GPX_BUFLEN);
+  ssize_t bufread;
+  
+  if (fd == -1) {
+    PARSE_ERROR(ctx, "Unable to open %s (errno=%s)", gpxfile, strerror(errno));
+    return false;
+  }
+  
+  if (read(fd, buffer, sizeof(bzip2_magic)) != sizeof(bzip2_magic)) {
+    PARSE_ERROR(ctx, "Unable to read data from %s (errno=%s)", gpxfile, strerror(errno));
+    close(fd);
+    return false;
+  }
+  
+  if (memcmp(buffer, bzip2_magic, sizeof(bzip2_magic)) != 0) {
+    close(fd);
+    return false;
+  }
+  
+  close(fd);
+  
+  f = BZ2_bzopen(gpxfile, "rb");
+  
+  INFO("Detected bzip2 encoded data");
+  
+  if (gpx_create_parser(ctx) == false) {
+    PARSE_ERROR(ctx, "Unable to create XML parser");
+    BZ2_bzclose(f);
+    return false;
+  }
+  
+  while ((bufread = BZ2_bzread(f, buffer, GPX_BUFLEN)) > 0) {
+    if (gpx_parse_buffer(ctx, buffer, bufread) == false) {
+      BZ2_bzclose(f);
+      return false;
+    }
+  }
+  
+  BZ2_bzclose(f);
+  
+  gpx_free_parser(ctx);
+  
+  return true;
+}
+
+GPX*
+gpx_parse_file(const char *gpxfile, char **err)
+{
+  GPXParseContext ctx;
+  
+  ctx.err = err;
+  
+  ctx.gpx = calloc(1, sizeof(GPX));
+  ctx.gpx->minlatitude = 91000000000;
+  ctx.gpx->minlongitude = 181000000000;
+  ctx.gpx->maxlatitude = -91000000000;
+  ctx.gpx->maxlongitude = -181000000000;
+  ctx.point = NULL;
+  ctx.lastpoint = NULL;
+  ctx.curseg = 0;
+  ctx.accumulator = NULL;
+  ctx.state = UNKNOWN;
+  ctx.accumulator_size = 0;
+  ctx.subfile = NULL;
+  
+  if (gpx_parse_archive(&ctx, gpxfile) == true) {
+    goto success;
+  } else {
+    if (*(ctx.err) != NULL) {
+      ERROR("Archive failure");
+      goto failure;
+    }
+  }
+  
+  if (gpx_try_gzip(&ctx, gpxfile) == true) {
+    goto success;
+  } else {
+    if (*(ctx.err) != NULL) {
+      ERROR("GZip failure");
+      goto failure;
+    }
+  }
+  
+  if (gpx_try_bzip2(&ctx, gpxfile) == true) {
+    goto success;
+  } else {
+    if (*(ctx.err) != NULL) {
+      ERROR("BZip2 failure");
+      goto failure;
+    }
+  }
+  
+  if (gpx_parse_plain_file(&ctx, gpxfile) == false) {
+    goto failure;
+  }
+  
+  if (ctx.gpx->goodpoints == 0) {
+    ERROR("Zero good points, %d bad (%d missed time, %d bad lat, %d bad long)",
+          ctx.gpx->badpoints,
+          ctx.gpx->missed_time,
+          ctx.gpx->bad_lat,
+          ctx.gpx->bad_long);
+    if (ctx.gpx->missed_time > ((ctx.gpx->badpoints * 3) >> 2)) {
+      *(ctx.err) = strdup("Found no good GPX points in the input data. At least 75% of the trackpoints lacked a <time> tag.");
+    } else {
+      *(ctx.err) = strdup("Found no good GPX points in the input data");
+    }
+    goto failure;
+  }
+  
+  success:
+  if (ctx.point)
+    free(ctx.point);
+  if (ctx.accumulator != NULL)
+    free(ctx.accumulator);
+  
+  return ctx.gpx;
+  
+  failure:
+  gpx_abort_context(&ctx);
+  return NULL;
+}
+
+void
+gpx_print(GPX *gpx)
+{
+  GPXTrackPoint *pt;
+  int pointnr;
+  
+  printf("minlat=%ld maxlat=%ld minlon=%ld maxlon=%ld\n",
+         gpx->minlatitude, gpx->maxlatitude,
+         gpx->minlongitude, gpx->maxlongitude);
+  
+  printf("goodpoints=%d badpoints=%d\n",
+         gpx->goodpoints,
+         gpx->badpoints);
+  return;
+  for (pt = gpx->points, pointnr = 1; pt != NULL; pt = pt->next, pointnr++)
+    printf("%4d: lat=%ld lon=%ld ele=%f time=%s seg=%d\n",
+           pointnr, pt->latitude, pt->longitude, pt->elevation,
+           pt->timestamp, pt->segment);
+  
+}
diff --git a/src/gpx.h b/src/gpx.h
new file mode 100644 (file)
index 0000000..1c4f0a3
--- /dev/null
+++ b/src/gpx.h
@@ -0,0 +1,51 @@
+/* gpx-import/src/gpx.h
+ *
+ * GPX structures and management
+ *
+ * Copyright Daniel Silverstone <dsilvers@digital-scurf.org>
+ *
+ * Written for the OpenStreetMap project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2 of the License
+ * only.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#ifndef GPX_IMPORT_GPX_H
+#define GPX_IMPORT_GPX_H
+
+#include <stdint.h>
+#include <sys/time.h>
+#include <time.h>
+
+typedef int64_t GPXCoord;
+
+typedef struct _GPXTrackPoint_s {
+  GPXCoord longitude, latitude, altitude;
+  char *timestamp;
+  uint32_t segment;
+  float elevation;
+  struct _GPXTrackPoint_s *next;
+} GPXTrackPoint;
+
+typedef struct {
+  GPXCoord firstlatitude, firstlongitude, minlatitude, minlongitude, maxlatitude, maxlongitude;
+  uint32_t goodpoints;
+  uint32_t badpoints;
+  uint32_t missed_time;
+  uint32_t bad_lat;
+  uint32_t bad_long;
+  GPXTrackPoint *points;
+} GPX;
+
+extern GPX* gpx_parse_file(const char *gpxfile, char **err);
+extern void gpx_free(GPX *gpx);
+extern void gpx_print(GPX *gpx);
+
+#endif
diff --git a/src/image.c b/src/image.c
new file mode 100644 (file)
index 0000000..9a96bbc
--- /dev/null
@@ -0,0 +1,181 @@
+/* gpx-import/src/image.c
+ *
+ * GPX Importer, thumbnail/icon and animation generator
+ *
+ * Copyright Daniel Silverstone <dsilvers@digital-scurf.org>
+ *
+ * Written for the OpenStreetMap project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2 of the License
+ * only.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <gd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+
+#include "image.h"
+#include "mercator.h"
+
+typedef struct {
+  uint32_t x, y;
+} XYPair;
+
+static XYPair *
+image_generate_coords(GPX *gpx, MercatorProjection *proj)
+{
+  XYPair *ret = calloc(gpx->goodpoints, sizeof(XYPair));
+  GPXTrackPoint *pt;
+  uint32_t n;
+  
+  for(n = 0, pt = gpx->points; n < gpx->goodpoints; ++n, pt = pt->next) {
+    mercator_projection_project(proj, pt->latitude, pt->longitude,
+                                &(ret[n].x), &(ret[n].y));
+  }
+  
+  return ret;
+}
+
+void
+image_generate_animation(GPX *gpx,
+                         const char *outfilename,
+                         uint32_t width,
+                         uint32_t height,
+                         uint32_t nframes)
+{
+  gdImagePtr frame[nframes];
+  FILE *out;
+  int black, white, grey;
+  XYPair *coords;
+  MercatorProjection *proj;
+  uint32_t n, oldx, oldy, curx, cury, pt;
+  uint32_t ptsper = gpx->goodpoints / nframes;
+  
+
+  proj = mercator_projection_new(gpx->minlatitude,
+                                 gpx->minlongitude,
+                                 gpx->maxlatitude,
+                                 gpx->maxlongitude,
+                                 width,
+                                 height);
+  
+  coords = image_generate_coords(gpx, proj);
+  
+  for (n = 0; n < nframes; ++n) {
+    gdImagePtr f;
+    f = frame[n] = gdImageCreate(width, height);
+    if (n == 0) {
+      black = gdImageColorAllocate(f, 0, 0, 0);
+      white = gdImageColorAllocate(f, 255, 255, 255);
+      grey = gdImageColorAllocate(f, 0xBB, 0xBB, 0xBB);
+    } else {
+      gdImagePaletteCopy(frame[n], frame[0]);
+    }
+    gdImageFilledRectangle(f, 0, 0, width, height, white);
+  }
+  
+  oldx = coords[0].x;
+  oldy = coords[0].y;
+  
+  for (pt = 1; pt < gpx->goodpoints; ++pt) {
+    curx = coords[pt].x;
+    cury = coords[pt].y;
+    
+    for (n = 0; n < nframes; ++n) {
+      if ((pt >= (ptsper * n)) && (pt <= (ptsper * (n+1)))) {
+        gdImageSetThickness(frame[n], 3);
+        gdImageSetAntiAliased(frame[n], black);
+      } else {
+        gdImageSetThickness(frame[n], 1);
+        gdImageSetAntiAliased(frame[n], grey);
+      }
+      gdImageLine(frame[n], oldx, oldy, curx, cury, gdAntiAliased);
+    }
+    
+    oldx = curx;
+    oldy = cury;
+  }
+  
+  out = fopen(outfilename, "wb");
+  if (out != NULL) {
+    gdImageGifAnimBegin(frame[0], out, 1, 0);
+    for (n = 0; n < nframes; ++n) {
+      gdImageGifAnimAdd(frame[n], out, 0, 0, 0, 50, gdDisposalNone, (n > 0) ? frame[n-1] : NULL);
+    }
+    gdImageGifAnimEnd(out);
+    fclose(out);
+  } else {
+    ERROR("Unable to create %s (errno=%s)", outfilename, strerror(errno));
+  }
+  
+  for (n = 0; n < nframes; ++n) {
+    gdImageDestroy(frame[n]);
+  }
+  
+  free(coords);
+  mercator_projection_free(proj);
+}
+
+void
+image_generate_icon(GPX *gpx,
+                    const char *outfilename,
+                    uint32_t width,
+                    uint32_t height)
+{
+  gdImagePtr icon;
+  FILE *out;
+  int black, white;
+  XYPair *coords;
+  MercatorProjection *proj;
+  uint32_t n, oldx, oldy, curx, cury;
+  
+  proj = mercator_projection_new(gpx->minlatitude,
+                                 gpx->minlongitude,
+                                 gpx->maxlatitude,
+                                 gpx->maxlongitude,
+                                 width,
+                                 height);
+  
+  icon = gdImageCreate(width, height);
+  
+  black = gdImageColorAllocate(icon, 0, 0, 0);
+  white = gdImageColorAllocate(icon, 255, 255, 255);
+  
+  gdImageFilledRectangle(icon, 0, 0, width, height, white);
+  
+  gdImageSetAntiAliased(icon, black);
+  
+  coords = image_generate_coords(gpx, proj);
+  
+  oldx = coords[0].x;
+  oldy = coords[0].y;
+  
+  for(n = 1; n < gpx->goodpoints; ++n) {
+    curx = coords[n].x;
+    cury = coords[n].y;
+    gdImageLine(icon, oldx, oldy, curx, cury, gdAntiAliased);
+    oldx = curx;
+    oldy = cury;
+  }
+  
+  out = fopen(outfilename, "wb");
+  
+  if (out != NULL) {
+    gdImageGif(icon, out);
+    fclose(out);
+  } else {
+    ERROR("Unable to create %s (errno=%s)", outfilename, strerror(errno));
+  }
+  
+  gdImageDestroy(icon);
+  free(coords);
+  mercator_projection_free(proj);
+}
diff --git a/src/image.h b/src/image.h
new file mode 100644 (file)
index 0000000..56ba988
--- /dev/null
@@ -0,0 +1,36 @@
+/* gpx-import/src/image.h
+ *
+ * GPX Importer, thumbnail/icon and animation generator
+ *
+ * Copyright Daniel Silverstone <dsilvers@digital-scurf.org>
+ *
+ * Written for the OpenStreetMap project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2 of the License
+ * only.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#ifndef GPX_IMPORT_IMAGE_H
+#define GPX_IMPORT_IMAGE_H
+
+#include "gpx.h"
+
+extern void image_generate_icon(GPX *gpx,
+                                const char *outfilename,
+                                uint32_t width,
+                                uint32_t height);
+
+extern void image_generate_animation(GPX *gpx,
+                                     const char *outfilename,
+                                     uint32_t width,
+                                     uint32_t height,
+                                     uint32_t nframes);
+
+#endif /* GPX_IMPORT_IMAGE_H */
diff --git a/src/interpolate.c b/src/interpolate.c
new file mode 100644 (file)
index 0000000..716f9d8
--- /dev/null
@@ -0,0 +1,126 @@
+/* gpx-import/src/interpolate.c
+ *
+ * GPX file importer, email interpolation
+ *
+ * Copyright Daniel Silverstone <dsilvers@digital-scurf.org>
+ *
+ * Written for the OpenStreetMap project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2 of the License
+ * only.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+
+#include <errno.h>
+
+#include "interpolate.h"
+
+static void
+do_interpolate(DBJob *job, FILE *input, FILE *output)
+{
+  int c;
+  
+  while ((c = fgetc(input)) != EOF) {
+    if (c != '%') {
+      fputc(c, output);
+      continue;
+    }
+    c = fgetc(input);
+    switch (c) {
+    case -1:
+    case '%':
+      fputc('%', output);
+      break;
+    case 'e':
+      fputs(job->email, output);
+      break;
+    case 'E':
+      fputs(job->error, output);
+      break;
+    case 't':
+      fputs(job->title, output);
+      break;
+    case 'd':
+      fputs(job->description, output);
+      break;
+    case 'g':
+      fprintf(output, "%d", job->gpx->goodpoints);
+      break;
+    case 'p':
+      fprintf(output, "%d", job->gpx->goodpoints + job->gpx->badpoints);
+      break;
+    case 'T':
+      if (strlen(job->tags) > 0) {
+        fputs("and the following tags:\n\n  ", output);
+        fputs(job->tags, output);
+      } else {
+        fputs("and no tags.", output);
+      }
+      break;
+    case 'm':
+      if (job->gpx->missed_time > 0) {
+        fprintf(output, "Of the failed points, %d lacked <time>", job->gpx->missed_time);
+      }
+      break;
+    case 'l':
+      if (job->gpx->bad_lat > 0) {
+        fprintf(output, "Of the failed points, %d had bad latitude", job->gpx->bad_lat);
+      }
+      break;
+    case 'L':
+      if (job->gpx->bad_long > 0) {
+        fprintf(output, "Of the failed points, %d had bad longitude", job->gpx->bad_long);
+      }
+      break;
+    default:
+      fputs("\n\n[Unknown % escape: ", output);
+      fputc(c, output);
+      fputs("]\n\n", output);
+    }
+  }
+}
+
+void
+interpolate(DBJob *job, const char *template)
+{
+  FILE *outputfile, *inputfile;
+  char inputpath[PATH_MAX];
+  
+  if (getenv("GPX_INTERPOLATE_STDOUT") != NULL) {
+    outputfile = stdout;
+  } else {
+    outputfile = popen("/usr/lib/sendmail -t -r '<>'", "w");
+    if (outputfile == NULL) {
+      ERROR("Unable to open sendmail! (errno=%s)", strerror(errno));
+      return;
+    }
+  }
+  
+  snprintf(inputpath, PATH_MAX, "%s/%s", getenv("GPX_PATH_TEMPLATES"), template);
+  
+  inputfile = fopen(inputpath, "rb");
+  
+  if (inputfile == NULL) {
+    ERROR("Unable to open input file %s (errno=%s)", inputpath, strerror(errno));
+  } else {
+    do_interpolate(job, inputfile, outputfile);
+    fclose(inputfile);
+  }
+  
+  if (outputfile != stdout) {
+    if (pclose(outputfile) == -1) {
+      ERROR("Failure while closing sendmail! (errno=%s)", strerror(errno));
+    }
+  }
+}
diff --git a/src/interpolate.h b/src/interpolate.h
new file mode 100644 (file)
index 0000000..210c60b
--- /dev/null
@@ -0,0 +1,27 @@
+/* gpx-import/src/interpolate.h
+ *
+ * GPX file importer, email interpolation
+ *
+ * Copyright Daniel Silverstone <dsilvers@digital-scurf.org>
+ *
+ * Written for the OpenStreetMap project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2 of the License
+ * only.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#ifndef GPX_INTERPOLATE_H
+#define GPX_INTERPOLATE_H
+
+#include "db.h"
+
+extern void interpolate(DBJob *job, const char *template);
+
+#endif /* GPX_INTERPOLATE_H */
diff --git a/src/log.c b/src/log.c
new file mode 100644 (file)
index 0000000..e2a007d
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,47 @@
+/* gpx-import/src/log.h
+ *
+ * GPX importer, logging primitives
+ *
+ * Copyright Daniel Silverstone <dsilvers@digital-scurf.org>
+ *
+ * Written for the OpenStreetMap project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2 of the License
+ * only.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <time.h>
+#include <string.h>
+
+#include "log.h"
+
+#define LOGBUFLEN 1024
+
+void
+_gpxlog(const char *level, const char *fmt, ...)
+{
+  char buffer[LOGBUFLEN];
+  va_list ap;
+  time_t ttnow;
+  struct tm tmnow;
+  
+  time(&ttnow);
+  gmtime_r(&ttnow, &tmnow);
+  
+  strftime(buffer, LOGBUFLEN, "[%Y-%m-%dT%H:%M:%SZ] ", &tmnow);
+  strcat(buffer, level);
+  strcat(buffer, ": ");
+  va_start(ap, fmt);
+  vsnprintf(buffer + strlen(buffer), LOGBUFLEN - strlen(buffer) - 1, fmt, ap);
+  va_end(ap);
+  puts(buffer);
+}
diff --git a/src/log.h b/src/log.h
new file mode 100644 (file)
index 0000000..6762a80
--- /dev/null
+++ b/src/log.h
@@ -0,0 +1,39 @@
+/* gpx-import/src/log.h
+ *
+ * GPX importer, logging primitives
+ *
+ * Copyright Daniel Silverstone <dsilvers@digital-scurf.org>
+ *
+ * Written for the OpenStreetMap project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2 of the License
+ * only.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#ifndef GPX_IMPORT_LOG_H
+#define GPX_IMPORT_LOG_H
+
+extern void _gpxlog(const char *level, const char *fmt, ...)
+  __attribute__ ((format (printf, 2, 3)));
+
+
+
+#ifdef NDEBUG
+#define DEBUG(X...)
+#else
+#define DEBUG(X...) _gpxlog("DEBUG", X)
+#endif
+
+#define INFO(X...) _gpxlog("INFO", X)
+#define WARN(X...) _gpxlog("WARN", X)
+#define ERROR(X...) _gpxlog("ERROR", X)
+
+
+#endif /* GPX_IMPORT_LOG_H */
diff --git a/src/main.c b/src/main.c
new file mode 100644 (file)
index 0000000..805ff55
--- /dev/null
@@ -0,0 +1,146 @@
+/* gpx-import/src/main.c
+ *
+ * GPX file importer
+ *
+ * Copyright Daniel Silverstone <dsilvers@digital-scurf.org>
+ *
+ * Written for the OpenStreetMap project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2 of the License
+ * only.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+
+#include "gpx.h"
+#include "db.h"
+#include "image.h"
+#include "filename.h"
+#include "interpolate.h"
+
+static bool needs_quit = false;
+
+static void
+do_quit(int ignored)
+{
+  (void)ignored;
+  
+  if (needs_quit == true) {
+    ERROR("Hard quit!");
+    exit(1);
+  }
+  needs_quit = true;
+}
+
+int
+main(int argc, char **argv)
+{
+  DBJob *job;
+  GPX *g;
+  int64_t gpxnr;
+  int sleep_time = atoi(getenv("GPX_SLEEP_TIME") ? getenv("GPX_SLEEP_TIME") : "0");
+  bool did_work = true;
+  clock_t cstart, cend;
+  
+  if (sleep_time == 0) {
+    sleep_time = 30;
+    WARN("Defaulted sleep time to %d seconds\n", sleep_time);
+  }
+  
+  INFO("Connecting to DB");
+  
+  if (db_connect() == false) {
+    ERROR("Unable to connect to DB");
+    return 1;
+  }
+  
+  signal(SIGHUP, do_quit);
+  signal(SIGINT, do_quit);
+  
+  do {
+    DEBUG("Looking for work");
+    cstart = clock();
+    job = db_find_work(sleep_time);
+    
+    if (job == NULL) {
+      /* Found no work, can we find an invisible trace to delete? */
+      gpxnr = db_find_invisible();
+      if (gpxnr != -1) {
+        INFO("Found invisible trace");
+        did_work = true;
+        db_destroy_trace(gpxnr);
+      }
+    }
+    
+    if (job == NULL) {
+      if (did_work == true)
+        INFO("No work to do, sleeping");
+      did_work = false;
+      sleep(sleep_time);
+      continue;
+    }
+    did_work = true;
+    
+    if (job->error == NULL) {
+      gpxnr = job->gpx_id;
+      INFO("Found job %ld, reading in...", gpxnr);
+      g = job->gpx = gpx_parse_file(make_filename("GPX_PATH_TRACES", job->gpx_id, ".gpx"), &(job->error));
+      
+      if (g != NULL && job->error == NULL) {
+        INFO("GPX contained %d good point(s) and %d bad point(s)", g->goodpoints, g->badpoints);
+        if (g->badpoints > 0) {
+          INFO("%d missed <time>, %d had bad latitude, %d had bad longitude",
+               g->missed_time, g->bad_lat, g->bad_long);
+        }
+        INFO("Creating icon and animation");
+        image_generate_icon(g, make_filename("GPX_PATH_IMAGES", gpxnr, "_icon.gif"), 50, 50);
+        image_generate_animation(g, make_filename("GPX_PATH_IMAGES", gpxnr, ".gif"), 250, 250, 10);
+        
+        if (db_insert_gpx(job) == false) {
+          db_error(job, "Issue while inserting job into database");
+          ERROR("Failure inserting into DB");
+        }
+      } else {
+        if (job->error == NULL)
+          job->error = strdup("XML failure while parsing GPX data");
+        ERROR("Failure while parsing GPX");
+      }
+    }
+    
+    if (job->error == NULL) {
+      /* Report success */
+      interpolate(job, "import-ok.eml");
+    } else {
+      /* Report failure */
+      interpolate(job, "import-bad.eml");
+      
+      /* Destroy this item */
+      db_destroy_trace(job->gpx_id);
+    }
+    
+    DEBUG("Cleaning up");
+    db_free_job(job);
+    cend = clock();
+    INFO("Import consumed %g CPU seconds", (double)(cend - cstart) / CLOCKS_PER_SEC);
+  } while (needs_quit == false);
+  
+  INFO("Disconnecting from DB");
+  
+  db_disconnect();
+  
+  DEBUG("Bye");
+  
+  return 0;
+}
diff --git a/src/mercator.c b/src/mercator.c
new file mode 100644 (file)
index 0000000..f2a7d23
--- /dev/null
@@ -0,0 +1,117 @@
+/* gpx-import/src/mercator.c
+ *
+ * GPX Importer, mercator projector
+ *
+ * Copyright Daniel Silverstone <dsilvers@digital-scurf.org>
+ *
+ * Written for the OpenStreetMap project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2 of the License
+ * only.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <stdlib.h>
+#include <math.h>
+
+#include "mercator.h"
+
+#ifndef MAX
+#define MAX(a,b) (((a) > (b)) ? (a) : (b))
+#endif
+
+struct _MercatorProjection {
+  uint32_t height, width;
+  double tx, ty, byty, bxtx;
+};
+
+static inline double
+mercator_double_from_coord(GPXCoord coord)
+{
+  return ((double)coord) / 1000000000.0;
+}
+
+static inline double
+mercator_sheet_x(double longitude)
+{
+  return longitude;
+}
+
+static inline double
+mercator_sheet_y(double latitude)
+{
+  if (latitude < -85.0511)
+    latitude = -85.0511;
+  else if (latitude > 85.0511)
+    latitude = 85.0511;
+  
+  return log(tan(M_PI / 4 + (latitude * M_PI / 180 / 2))) / (M_PI / 180);
+}
+
+void
+mercator_projection_free(MercatorProjection *projection)
+{
+  free(projection);
+}
+
+void
+mercator_projection_project(MercatorProjection *projection,
+                            GPXCoord _latitude,
+                            GPXCoord _longitude,
+                            uint32_t *x,
+                            uint32_t *y)
+{
+  double latitude = mercator_double_from_coord(_latitude);
+  double longitude = mercator_double_from_coord(_longitude);
+  *x = ((mercator_sheet_x(longitude) - projection->tx) / projection->bxtx) * (double)(projection->width);
+  *y = (double)(projection->height) - (((mercator_sheet_y(latitude) - projection->ty) / projection->byty) * (double)(projection->height));
+}
+
+MercatorProjection *
+mercator_projection_new(GPXCoord _min_latitude,
+                        GPXCoord _min_longitude,
+                        GPXCoord _max_latitude,
+                        GPXCoord _max_longitude,
+                        uint32_t width,
+                        uint32_t height)
+{
+  MercatorProjection *ret = calloc(1, sizeof(MercatorProjection));
+  double xsize, ysize, xscale, yscale, scale, xpad, ypad, bx, by;
+  double min_latitude, min_longitude, max_latitude, max_longitude;
+  
+  ret->height = height;
+  ret->width = width;
+  
+  min_latitude = mercator_double_from_coord(_min_latitude);
+  min_longitude = mercator_double_from_coord(_min_longitude);
+  max_latitude = mercator_double_from_coord(_max_latitude);
+  max_longitude = mercator_double_from_coord(_max_longitude);
+  
+  xsize = mercator_sheet_x(max_longitude) - mercator_sheet_x(min_longitude);
+  ysize = mercator_sheet_y(max_latitude) - mercator_sheet_y(min_latitude);
+  
+  xscale = xsize / width;
+  yscale = ysize / height;
+  
+  scale = MAX(xscale, yscale);
+  
+  xpad = ((double)width * scale) - xsize;
+  ypad = ((double)height * scale) - ysize;
+  
+  ret->tx = mercator_sheet_x(min_longitude) - (xpad / 2);
+  ret->ty = mercator_sheet_y(min_latitude) - (ypad / 2);
+  
+  bx = mercator_sheet_x(max_longitude) + (xpad / 2);
+  by = mercator_sheet_y(max_latitude) + (ypad / 2);
+  
+  ret->byty = by - ret->ty;
+  ret->bxtx = bx - ret->tx;
+  
+  return ret;
+}
diff --git a/src/mercator.h b/src/mercator.h
new file mode 100644 (file)
index 0000000..f57c5eb
--- /dev/null
@@ -0,0 +1,42 @@
+/* gpx-import/src/mercator.h
+ *
+ * GPX Importer, mercator projector
+ *
+ * Copyright Daniel Silverstone <dsilvers@digital-scurf.org>
+ *
+ * Written for the OpenStreetMap project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2 of the License
+ * only.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#ifndef GPX_IMPORT_MERCATOR_H
+#define GPX_IMPORT_MERCATOR_H
+
+#include "gpx.h"
+
+typedef struct _MercatorProjection MercatorProjection;
+
+extern MercatorProjection *mercator_projection_new(GPXCoord _min_latitude,
+                                                   GPXCoord _min_longitude,
+                                                   GPXCoord _max_latitude,
+                                                   GPXCoord _max_longitude,
+                                                   uint32_t width,
+                                                   uint32_t height);
+
+extern void mercator_projection_project(MercatorProjection *projection,
+                                        GPXCoord _latitude,
+                                        GPXCoord _longitude,
+                                        uint32_t *x,
+                                        uint32_t *y);
+
+extern void mercator_projection_free(MercatorProjection *projection);
+
+#endif
diff --git a/src/mysql.c b/src/mysql.c
new file mode 100644 (file)
index 0000000..4b0f3e2
--- /dev/null
@@ -0,0 +1,214 @@
+/* gpx-import/src/mysql.c
+ *
+ * GPS point insertion into MySQL database
+ *
+ * Copyright Daniel Silverstone <dsilvers@digital-scurf.org>
+ *
+ * Written for the OpenStreetMap project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2 of the License
+ * only.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <stdlib.h>
+#include <mysql.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "filename.h"
+#include "db.h"
+#include "quadtile.h"
+
+#define STMT_BUFLEN (1024 * 256)
+
+static MYSQL *handle;
+static char statement_buffer[STMT_BUFLEN];
+static char escape_buffer[STMT_BUFLEN];
+
+#define STMT(V...)                                                      \
+  do {                                                                  \
+    int stmt_len = snprintf(statement_buffer, STMT_BUFLEN, V);          \
+    if (mysql_real_query(handle, statement_buffer, stmt_len) != 0) {    \
+      return false;                                                     \
+    }                                                                   \
+  } while (0)
+
+#define BLANKOR(S) strdup(((S) ? (S) : ("")))
+
+bool
+db_destroy_trace(int64_t jobnr)
+{
+  INFO("Destroying job %ld", jobnr);
+  STMT("DELETE FROM gpx_file_tags WHERE gpx_id=%ld", jobnr);
+  STMT("DELETE FROM gps_points WHERE gpx_id=%ld", jobnr);
+  STMT("DELETE FROM gpx_files WHERE id=%ld", jobnr);
+  unlink(make_filename("GPX_PATH_TRACES", jobnr, ".gpx"));
+  unlink(make_filename("GPX_PATH_IMAGES", jobnr, "_icon.gif"));
+  unlink(make_filename("GPX_PATH_IMAGES", jobnr, ".gif"));
+  return true;
+}
+
+
+bool
+db_insert_gpx(DBJob *job)
+{
+  MYSQL_RES *res;
+  MYSQL_ROW row;
+  bool do_delete = false;
+  GPXTrackPoint *pt;
+  int64_t gpxnr = job->gpx_id;
+  GPX *gpx = job->gpx;
+  
+  STMT("SELECT COUNT(*) FROM gps_points WHERE gpx_id=%ld", gpxnr);
+  res = mysql_store_result(handle);
+  row = mysql_fetch_row(res);
+  if (atoi(row[0]) != 0) {
+    do_delete = true;
+  }
+  mysql_free_result(res);
+  
+  if (do_delete == true) {
+    WARN("Old rows detected, deleting");
+    STMT("DELETE FROM gps_points WHERE gpx_id=%ld", gpxnr);
+  }
+  
+  INFO("Inserting %d points", gpx->goodpoints);
+  
+  /* Iterate the points, inserting them into the DB */
+  for (pt = gpx->points; pt != NULL; pt = pt->next) {
+    mysql_real_escape_string(handle, escape_buffer, pt->timestamp, strlen(pt->timestamp));
+    STMT("INSERT INTO gps_points (gpx_id, trackid, latitude, longitude, timestamp, altitude, tile) " \
+         "VALUES (%ld, %d, %ld, %ld, '%s', %f, %u)",
+         gpxnr, pt->segment, pt->latitude / 100, pt->longitude / 100, escape_buffer, pt->elevation,
+         quadtile_for_coords(pt->latitude, pt->longitude));
+  }
+
+  /* Last up, update the GPX with our lat/long/numpoints etc */
+  STMT("UPDATE gpx_files SET inserted=1, size=%d, latitude=%g, longitude=%g WHERE id=%ld\n",
+       gpx->goodpoints, (double)gpx->firstlatitude / 1000000000.0, (double)gpx->firstlongitude / 1000000000.0, gpxnr);
+  
+  return true;
+}
+
+int64_t
+db_find_invisible(void)
+{
+  int64_t ret = -1;
+  MYSQL_RES *res;
+  MYSQL_ROW row;
+  
+  STMT("SELECT id FROM gpx_files WHERE visible=0 LIMIT 1");
+  
+  res = mysql_store_result(handle);
+  if (res != NULL) {
+    row = mysql_fetch_row(res);
+    if (row != NULL) {
+      ret = strtol(row[0], NULL, 0);
+    }
+    mysql_free_result(res);
+  }
+  return ret;
+}
+
+DBJob *
+db_find_work(int minage)
+{
+  DBJob *ret = NULL;
+  MYSQL_RES *res;
+  MYSQL_ROW row;
+  int64_t user;
+  
+  STMT("SELECT id, name, description, user_id FROM gpx_files WHERE visible=1 AND inserted=0 AND timestamp <= now() - %d ORDER BY timestamp ASC LIMIT 1", minage);
+  res = mysql_store_result(handle);
+  if (res != NULL) {
+    row = mysql_fetch_row(res);
+    if (row != NULL) {
+      ret = calloc(1, sizeof(DBJob));
+      ret->gpx_id = strtol(row[0], NULL, 0);
+      ret->title = BLANKOR(row[1]);
+      ret->description = BLANKOR(row[2]);
+      user = strtol(row[3], NULL, 0);
+    }
+    mysql_free_result(res);
+  }
+  
+  if (ret != NULL) {
+    /* Attempt to retrieve the email address */
+    STMT("SELECT display_name, email FROM users WHERE id=%ld", user);
+    res = mysql_store_result(handle);
+    if (res != NULL) {
+      row = mysql_fetch_row(res);
+      if (row != NULL) {
+        int tlen = strlen(row[0]) + strlen(row[1]) + 4; /* space '<' '>' NULL */
+        ret->email = malloc(tlen);
+        snprintf(ret->email, tlen, "%s <%s>", row[0], row[1]);
+      } else {
+        db_error(ret, "Unable to find user information for user %ld", user);
+      }
+      mysql_free_result(res);
+    } else {
+      db_error(ret, "Database error while retrieving user information for user %ld", user);
+    }
+  }
+  
+  if (ret != NULL && ret->error == NULL) {
+    /* Attempt to retrieve the tags */
+    STMT("SELECT COALESCE(GROUP_CONCAT(tag), '') AS tags FROM gpx_file_tags WHERE gpx_id=%ld", ret->gpx_id);
+    res = mysql_store_result(handle);
+    if (res != NULL) {
+      row = mysql_fetch_row(res);
+      if (row != NULL) {
+        ret->tags = BLANKOR(row[0]);
+      } else {
+        db_error(ret, "Unable to retrieve GPX tags for file %ld\n", ret->gpx_id);
+      }
+      mysql_free_result(res);
+    } else {
+      db_error(ret, "Database error while retrieving GPX tags for file %ld\n", ret->gpx_id);
+    }
+  }
+  
+  return ret;
+}
+
+bool
+db_connect(void)
+{
+  char *host, *user, *pass, *db;
+  int port;
+  /* Establish connection to MySQL using environment */
+  mysql_library_init(0, NULL, NULL);
+  handle = mysql_init(NULL);
+  if (handle == NULL)
+    return false;
+  
+  host = getenv("GPX_MYSQL_HOST");
+  user = getenv("GPX_MYSQL_USER");
+  pass = getenv("GPX_MYSQL_PASS");
+  db = getenv("GPX_MYSQL_DB");
+  port = (getenv("GPX_MYSQL_PORT") ? atoi(getenv("GPX_MYSQL_PORT")) : 0);
+  
+  if (mysql_real_connect(handle, host, user, pass, db, port, NULL, 0) == NULL) {
+    ERROR("Failure connecting to MySQL server: %s", mysql_error(handle));
+    mysql_close(handle);
+    mysql_library_end();
+    return false;
+  }
+  
+  return true;
+}
+
+void
+db_disconnect(void)
+{
+  mysql_close(handle);
+  mysql_library_end();
+}
diff --git a/src/quadtile.c b/src/quadtile.c
new file mode 100644 (file)
index 0000000..0367c8c
--- /dev/null
@@ -0,0 +1,58 @@
+/* gpx-import/src/quadtile.c
+ *
+ * GPX importer, quadtile calculation
+ *
+ * Copyright Daniel Silverstone <dsilvers@digital-scurf.org>
+ *
+ * Written for the OpenStreetMap project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2 of the License
+ * only.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <math.h>
+
+#include "quadtile.h"
+
+/* This code is stolen from sites/rails_port/lib/quad_tile/quad_tile.h */
+
+static inline unsigned int
+xy2tile(unsigned int x, unsigned int y)
+{
+   unsigned int tile = 0;
+   int          i;
+
+   for (i = 15; i >= 0; i--)
+   {
+      tile = (tile << 1) | ((x >> i) & 1);
+      tile = (tile << 1) | ((y >> i) & 1);
+   }
+
+   return tile;
+}
+
+static inline unsigned int
+lon2x(double lon)
+{
+   return round((lon + 180.0) * 65535.0 / 360.0);
+}
+
+static inline unsigned int
+lat2y(double lat)
+{
+   return round((lat + 90.0) * 65535.0 / 180.0);
+}
+
+uint32_t
+quadtile_for_coords(GPXCoord latitude, GPXCoord longitude)
+{
+  return xy2tile(lon2x((double)longitude / 1000000000.0),
+                 lat2y((double)latitude / 1000000000.0));
+}
diff --git a/src/quadtile.h b/src/quadtile.h
new file mode 100644 (file)
index 0000000..99224ac
--- /dev/null
@@ -0,0 +1,27 @@
+/* gpx-import/src/quadtile.h
+ *
+ * GPX importer, quadtile calculation
+ *
+ * Copyright Daniel Silverstone <dsilvers@digital-scurf.org>
+ *
+ * Written for the OpenStreetMap project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2 of the License
+ * only.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#ifndef GPX_QUADTILE_H
+#define GPX_QUADTILE_H
+
+#include "gpx.h"
+
+extern uint32_t quadtile_for_coords(GPXCoord latitude, GPXCoord longitude);
+
+#endif /* GPX_QUADTILE_H */
diff --git a/templates/README b/templates/README
new file mode 100644 (file)
index 0000000..414dfd2
--- /dev/null
@@ -0,0 +1,24 @@
+This directory contains templates which are used by the importer to
+send emails etc.
+
+Interpolations are %X where X is:
+
+  e = email address of recipient, formatted for SMTP
+      so Real Name If Available <email@domain>
+
+  E = Any error which was encountered while processing the job
+
+  t = Title of GPX trace
+
+  T = Any tags, preceeded by "and the following tags\n"
+      or "and no tags." if there were no tags in the GPX import
+
+  d = The description of the GPX trace
+
+  g = Number of good points imported from the trace
+
+  p = Number of points in the trace in total.
+
+  m = Number of points which missed a <time> tag
+  l = Number of points with bad latitude
+  L = Number of points with bad longitude
diff --git a/templates/import-bad.eml b/templates/import-bad.eml
new file mode 100644 (file)
index 0000000..acc8213
--- /dev/null
@@ -0,0 +1,26 @@
+From: webmaster@openstreetmap.org
+To: %e
+Subject: [OpenStreetMap] GPX Import Failure
+Auto-Submitted: auto-generated
+
+Hi,
+
+It looks like your GPX file
+
+  %t
+
+with the description
+
+  %d
+
+%T
+
+failed to import. Here's the error:
+
+  %E
+
+More information about GPX import failures and how to avoid
+them can be found at:
+
+  http://wiki.openstreetmap.org/index.php/GPX_Import_Failures
+
diff --git a/templates/import-ok.eml b/templates/import-ok.eml
new file mode 100644 (file)
index 0000000..3ab77a8
--- /dev/null
@@ -0,0 +1,22 @@
+From: webmaster@openstreetmap.org
+To: %e
+Subject: [OpenStreetMap] GPX Import Success
+Auto-Submitted: auto-generated
+
+Hi,
+
+It looks like your GPX file
+
+  %t
+
+with the description
+
+  %d
+
+%T
+
+loaded successfully with %g out of a possible %p points.
+
+%m
+%l
+%L