[Git][debian-gis-team/routino][master] 5 commits: New upstream version 3.3.1

Bas Couwenberg gitlab at salsa.debian.org
Mon Sep 9 05:50:04 BST 2019



Bas Couwenberg pushed to branch master at Debian GIS Project / routino


Commits:
87a9a0ae by Bas Couwenberg at 2019-09-09T04:23:42Z
New upstream version 3.3.1
- - - - -
faa42cdf by Bas Couwenberg at 2019-09-09T04:23:50Z
Update upstream source from tag 'upstream/3.3.1'

Update to upstream version '3.3.1'
with Debian dir c09eaffb1b7908d62415b58305ea9721ef70d064
- - - - -
f246fbb1 by Bas Couwenberg at 2019-09-09T04:25:37Z
New upstream release.

- - - - -
fd215699 by Bas Couwenberg at 2019-09-09T04:26:08Z
Drop python.patch, fixed upstream.

- - - - -
fd0b60b5 by Bas Couwenberg at 2019-09-09T04:41:01Z
Set distribution to unstable.

- - - - -


21 changed files:

- ChangeLog
- debian/changelog
- − debian/patches/python.patch
- debian/patches/series
- doc/NEWS.txt
- doc/README.txt
- doc/html/readme.html
- + python/Makefile
- + python/README.txt
- + python/database.py
- + python/router.py
- + python/setup.py
- + python/src/__init__.py
- + python/src/database.cc
- + python/src/database.hh
- + python/src/database.i
- + python/src/router.i
- + python/test/Makefile
- + python/test/run-database-tests.sh
- + python/test/run-one-test.sh
- + python/test/run-router-tests.sh


Changes:

=====================================
ChangeLog
=====================================
@@ -1,3 +1,20 @@
+2019-09-08  Andrew M. Bishop <amb>
+
+	Version 3.3.1 released.
+
+2019-09-08 [r2019]  Andrew M. Bishop <amb>
+
+	* FILES, doc/NEWS.txt, doc/README.txt, doc/html/readme.html: Update
+	  for version 3.3.1 release.
+
+2019-09-08 [r2017-2018]  Andrew M. Bishop <amb>
+
+	* python/Makefile: Make sure that 'make clean' deletes all files
+	  generated by swig.
+
+	* python/README.txt: Correct tiny mistake in documentation
+	  formatting.
+
 2019-09-07  Andrew M. Bishop <amb>
 
 	Version 3.3 released.


=====================================
debian/changelog
=====================================
@@ -1,3 +1,10 @@
+routino (3.3.1-1) unstable; urgency=medium
+
+  * New upstream release.
+  * Drop python.patch, fixed upstream.
+
+ -- Bas Couwenberg <sebastic at debian.org>  Mon, 09 Sep 2019 06:40:48 +0200
+
 routino (3.3-1) unstable; urgency=medium
 
   * New upstream release.


=====================================
debian/patches/python.patch deleted
=====================================
@@ -1,14 +0,0 @@
-Description: Remove python subdir, not included in upstream tarball.
-Author: Bas Couwenberg <sebastic at debian.org>
-
---- a/Makefile
-+++ b/Makefile
-@@ -24,7 +24,7 @@ include Makefile.conf
- 
- # Sub-directories and sub-makefiles
- 
--SUBDIRS=src xml doc web extras python
-+SUBDIRS=src xml doc web extras
- 
- ########
- 


=====================================
debian/patches/series
=====================================
@@ -6,4 +6,3 @@ mapprops
 hardening
 #map_bounds
 #use_openlayers
-python.patch


=====================================
doc/NEWS.txt
=====================================
@@ -1,3 +1,14 @@
+Version 3.3.1 of Routino released : Sun Sep 8 2019
+--------------------------------------------------
+
+Bug fixes:
+  Ensure that 'make clean' in the python directory deletes auto-generated files.
+  Include the python directory in the release file (include in 'FILES').
+
+
+Note: This version is compatible with databases from versions 2.7.1 - 3.3.
+
+
 Version 3.3 of Routino released : Sat Sep 7 2019
 ------------------------------------------------
 


=====================================
doc/README.txt
=====================================
@@ -135,6 +135,7 @@ Status
    Version 3.1.1 of Routino was released on 6th March 2016.
    Version 3.2 of Routino was released on 12th March 2017.
    Version 3.3 of Routino was released on 7th September 2019.
+   Version 3.3.1 of Routino was released on 8th September 2019.
 
    The full version history is available in the NEWS.txt file.
 


=====================================
doc/html/readme.html
=====================================
@@ -222,13 +222,27 @@ Version 3.1.1 of Routino was released on 6th March 2016.
 Version 3.2 of Routino was released on 12th March 2017.
 <br>
 Version 3.3 of Routino was released on 7th September 2019.
+<br>
+Version 3.3.1 of Routino was released on 8th September 2019.
 
 <p>
 
 The full version history is available in the NEWS.txt file.
 
 
-<h3 id="H_1_5_1" title="Changes 3.3">Changes in Version 3.3</h3>
+<h3 id="H_1_5_1" title="Changes 3.3.1">Changes in Version 3.3.1</h3>
+
+<dl>
+  <dt>Bug fixes:
+    <dd>Ensure that 'make clean' in the python directory deletes auto-generated files.
+    <br>Include the python directory in the release file (include in 'FILES').
+</dl>
+
+<p>
+<b>Note:</b> This version is compatible with databases from versions 2.7.1 - 3.3.
+
+
+<h3 id="H_1_5_2" title="Changes 3.3">Changes in Version 3.3</h3>
 
 <dl>
   <dt>Bug fixes:


=====================================
python/Makefile
=====================================
@@ -0,0 +1,130 @@
+# Python interface Makefile
+#
+# Part of the Routino routing software.
+#
+# This file Copyright 2018, 2019 Andrew M. Bishop
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# All configuration is in the top-level Makefile.conf
+
+include ../Makefile.conf
+
+# Programs
+
+PYTHON=python3
+
+SWIG=swig
+
+# Compilation targets
+
+PY_FILES=$(wildcard src/*.py)
+
+C_FILES=$(wildcard src/*.c)
+CC_FILES=$(wildcard src/*.cc)
+
+SWIG_C=src/_router.c
+SWIG_CC=src/_database.cc
+SWIG_PY=src/router.py src/database.py
+
+ifneq ($(HOST),MINGW)
+  LIBROUTINO=../src/libroutino.so
+else
+  LIBROUTINO=../src/routino.dll
+endif
+
+BUILD_TIMESTAMP=build/.timestamp
+
+# Check that we have Python3 and swig installed
+
+HAVE_PYTHON=$(shell $(PYTHON) --version 2> /dev/null)
+
+HAVE_SWIG=$(shell $(SWIG) -version 2> /dev/null)
+
+ifeq ($(HAVE_PYTHON),)
+  $(warning Python3 not installed - skipping Python module creation)
+endif
+
+ifeq ($(HAVE_SWIG),)
+  $(warning Swig not installed - skipping Python module creation)
+endif
+
+########
+
+all: $(and $(HAVE_SWIG),$(HAVE_PYTHON),all-if-python)
+
+all-if-python: $(BUILD_TIMESTAMP)
+
+########
+
+$(BUILD_TIMESTAMP): $(SWIG_C) $(SWIG_CC) $(SWIG_PY) $(PY_FILES) $(C_FILES) $(CC_FILES) $(LIBROUTINO) setup.py
+	@rm -f $@
+	$(PYTHON) setup.py build && touch $(BUILD_TIMESTAMP)
+
+src/_router.c : src/router.i ../src/routino.h
+	$(SWIG) -python -o $@ $<
+
+src/_database.cc : src/database.i src/database.hh
+	$(SWIG) -c++ -python -o $@ $<
+
+src/%.o : src/%.c
+	$(CC) -c $(CFLAGS) $< -o $@
+
+src/%.o : src/%.cc
+	$(CXX) -c $(CFLAGS) $< -o $@
+
+$(LIBROUTINO):
+	cd ../src && $(MAKE) all-lib
+
+########
+
+test: $(and $(HAVE_SWIG),$(HAVE_PYTHON),test-if-python)
+
+test-if-python: $(BUILD_TIMESTAMP)
+	cd test && $(MAKE) test
+
+########
+
+install: $(and $(HAVE_SWIG),$(HAVE_PYTHON),install-if-python)
+
+install-if-python: all
+	$(PYTHON) setup.py install --prefix $(prefix)
+
+########
+
+clean: clean-local
+	cd test && $(MAKE) $@
+
+clean-local:
+	rm -f *~
+	rm -rf build
+	rm -f $(SWIG_C)
+	rm -f $(SWIG_CC)
+	rm -f $(SWIG_PY)
+
+########
+
+distclean: distclean-local
+	cd test && $(MAKE) $@
+
+distclean-local: clean-local
+
+########
+
+.PHONY:: all test install clean distclean
+
+.PHONY:: all-if-python test-if-python install-if-python
+
+.PHONY:: clean-local distclean-local


=====================================
python/README.txt
=====================================
@@ -0,0 +1,56 @@
+                                 ROUTINO PYTHON
+                                 ==============
+
+This directory contains a Python version 3 interface to the Routino routing
+database that allows routes to be calculated and the database to be accessed.
+
+Compilation
+-----------
+
+To compile the Python module run 'make'.  A working Python 3 installation and
+the Swig tool are required to be able to compile this Python module.  If they
+are not available then a warning will be printed but no error occur.
+
+Running 'make' in the top level directory will also try to build the module.
+
+Testing
+-------
+
+To run the test scripts run 'make test'.  The tests verify that the results of
+the Python version are identical to the results of the compiled version.
+
+Running 'make test' in the top level directory will also try to run the tests
+for the Python module.
+
+Installation
+------------
+
+To install the Python module run 'make install'.  The installation directory is
+the one defined in 'Makefile.conf'.
+
+Running 'make install' in the top level directory will also try to install the
+module.
+
+Using - Router
+--------------
+
+To use the Python module normally it must be installed and the libroutino
+library must also be installed in a directory that is searched for libraries.
+
+The Python example router 'router.py' accepts the same command line arguments as
+the compiled versions.
+
+The Python module supports exactly the same functionality as the Routino library
+(libroutino) because it is implemented simply as a wrapper around that library.
+The documentation for using the library (and therefore the Python module) is
+available in the files "doc/LIBRARY.txt" and "doc/html/library.html".
+
+Using - Database
+----------------
+
+To use the Python module normally it must be installed, the libroutino library
+is not required for the database access functions.
+
+The Python script 'database.py' is an example of using the Python module for
+accessing a Routino database (one created by 'make test').  No further
+documentation is provided, all possible features are used in the example script.


=====================================
python/database.py
=====================================
@@ -0,0 +1,272 @@
+#!/usr/bin/python3
+##########################################
+# Routino database access from Python.
+#
+# Part of the Routino routing software.
+##########################################
+# This file Copyright 2018 Andrew M. Bishop
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+##########################################
+
+import routino.database
+
+
+# Database, access all attributes
+
+database = routino.database.LoadDatabase("../../src/test/fat", "turns")
+
+if database is None:
+    database = routino.database.LoadDatabase("../src/test/fat", "turns")
+
+    if database is None:
+        print("Failed to load database")
+        exit(1)
+
+print(database)
+
+database_attrs = ['nnodes', 'nsegments', 'nways', 'nrelations']
+
+for attr in database_attrs:
+    print("   Attribute: " + attr + " =", getattr(database, attr))
+
+print("")
+
+
+# A single node, access all attributes and all functions
+
+node=database.GetNode(0)
+
+print("1st node =", node)
+
+node_attrs = ['id', 'firstsegment', 'latitude', 'longitude', 'allow',     'flags']
+node_infos = ['',   '',              'degrees', 'degrees',   '[note 1]',  '[note 2]']
+
+for attr,info in zip(node_attrs,node_infos):
+    print("   Attribute: " + attr + " =", getattr(node, attr), info)
+
+segments = node.Segments()
+print("   Function: " + "Segments()" + " = [" + ", ".join([str(segments[x]) for x in range(len(segments))]) + "]")
+
+print("")
+
+
+# A single segment, access all attributes and all functions
+
+segment=database.GetSegment(0)
+
+print("1st segment =", segment)
+
+segment_attrs = ['id', 'node1', 'node2', 'next2', 'way', 'distance', 'flags']
+segment_infos = ['',   '',      '',      '',      '',    'km',       '[note 3]']
+
+for attr,info in zip(segment_attrs,segment_infos):
+    print("   Attribute: " + attr + " =", getattr(segment, attr), info)
+
+print("   Function: " + "Node1()" + " = " + str(segment.Node1()))
+print("   Function: " + "Node2()" + " = " + str(segment.Node2()))
+print("   Function: " + "Way()" + " = " + str(segment.Way()))
+
+print("")
+
+
+# A single way, access all attributes and all functions
+
+way=database.GetWay(0)
+
+print("1st way =", way)
+
+way_attrs = ['id', 'name', 'allow',    'type',     'props',    'speed',          'weight',          'height',          'width',           'length']
+way_infos = ['',   '',     '[note 1]', '[note 4]', '[note 5]', 'km/hr [note 6]', 'tonnes [note 6]', 'metres [note 6]', 'metres [note 6]', 'metres [note 6]']
+
+for attr,info in zip(way_attrs,way_infos):
+    print("   Attribute: " + attr + " =", getattr(way, attr), info)
+
+print("")
+
+
+# A single relation, access all attributes and all functions
+
+relation=database.GetRelation(0)
+
+print("1st relation =", relation)
+
+relation_attrs = ['id', 'from_seg', 'via_node', 'to_seg', 'from_way', 'to_way', 'from_node', 'to_node', 'except_transport']
+relation_infos = ['',   '',         '',         '',       '',         '',       '',          '',        '[note 7]']
+
+for attr,info in zip(relation_attrs,relation_infos):
+    print("   Attribute: " + attr + " =", getattr(relation, attr), info)
+
+print("   Function: " + "FromSegment()" + " = " + str(relation.FromSegment()))
+print("   Function: " + "ViaNode()" + " = " + str(relation.ViaNode()))
+print("   Function: " + "ToSegment()" + " = " + str(relation.ToSegment()))
+
+print("   Function: " + "FromWay()" + " = " + str(relation.FromWay()))
+print("   Function: " + "ToWay()" + " = " + str(relation.ToWay()))
+
+print("   Function: " + "FromNode()" + " = " + str(relation.FromNode()))
+print("   Function: " + "ToNode()" + " = " + str(relation.ToNode()))
+
+print("")
+
+
+# The list of nodes as a list and an iterable (just the first 4)
+
+nodes=database.Nodes()
+
+print("len(database.Nodes()) = " + str(len(nodes)))
+
+print("database.Nodes() = [" + ", ".join([str(nodes[x]) for x in range(4)]) + ", ...]")
+
+for node in nodes:
+    if node.id == 4:
+        break
+    print(node)
+
+print("")
+
+
+# The list of segments as a list and an iterable (just the first 4)
+
+segments=database.Segments()
+
+print("len(database.Segments()) = " + str(len(segments)))
+
+print("database.Segments() = [" + ", ".join([str(segments[x]) for x in range(4)]) + ", ...]")
+
+for segment in segments:
+    if segment.id == 4:
+        break
+    print(segment)
+
+print("")
+
+
+# The list of ways as a list and an iterable (just the first 4)
+
+ways=database.Ways()
+
+print("len(database.Ways()) = " + str(len(ways)))
+
+print("database.Ways() = [" + ", ".join([str(ways[x]) for x in range(4)]) + ", ...]")
+
+for way in ways:
+    if way.id == 4:
+        break
+    print(way)
+
+print("")
+
+
+# The list of relations as a list and an iterable (just the first 4)
+
+relations=database.Relations()
+
+print("len(database.Relations()) = " + str(len(relations)))
+
+print("database.Relations() = [" + ", ".join([str(relations[x]) for x in range(4)]) + ", ...]")
+
+for relation in relations:
+    if relation.id == 4:
+        break
+    print(relation)
+
+print("")
+
+
+# Enumerated lists
+
+transports_enum = ["Transports_None",
+                   "Transports_Foot",
+                   "Transports_Horse",
+                   "Transports_Wheelchair",
+                   "Transports_Bicycle",
+                   "Transports_Moped",
+                   "Transports_Motorcycle",
+                   "Transports_Motorcar",
+                   "Transports_Goods",
+                   "Transports_HGV",
+                   "Transports_PSV",
+                   "Transports_ALL"]
+
+nodeflags_enum = ["Nodeflag_Super",
+                  "Nodeflag_U_Turn",
+                  "Nodeflag_Mini_Roundabout",
+                  "Nodeflag_Turn_Restrict",
+                  "Nodeflag_Turn_Restrict2"]
+
+segmentflags_enum = ["Segmentflag_Area",
+                     "Segmentflag_Oneway_1to2",
+                     "Segmentflag_Oneway_2to1",
+                     "Segmentflag_Super",
+                     "Segmentflag_Normal"]
+
+properties_enum = ["Properties_None",
+                   "Properties_Paved",
+                   "Properties_Multilane",
+                   "Properties_Bridge",
+                   "Properties_Tunnel",
+                   "Properties_FootRoute",
+                   "Properties_BicycleRoute",
+                   "Properties_ALL"]
+
+highway_enum = ["Highway_Motorway",
+                "Highway_Trunk",
+                "Highway_Primary",
+                "Highway_Secondary",
+                "Highway_Tertiary",
+                "Highway_Unclassified",
+                "Highway_Residential",
+                "Highway_Service",
+                "Highway_Track",
+                "Highway_Cycleway",
+                "Highway_Path",
+                "Highway_Steps",
+                "Highway_Ferry",
+                "Highway_Count",
+                "Highway_CycleBothWays",
+                "Highway_OneWay",
+                "Highway_Roundabout",
+                "Highway_Area"]
+
+def print_enum(list):
+    for item in list:
+        print("    routino.database."+item)
+
+
+print("Note 1: The Node's and Way's 'allow' parameter can be the combination of these enumerated values:")
+print_enum(transports_enum)
+print("")
+print("Note 2: The Node's 'flags' parameter can be the combination of these enumerated values:")
+print_enum(nodeflags_enum)
+print("")
+print("Note 3: The Segment's 'flags' parameter can be the combination of these enumerated values:")
+print_enum(segmentflags_enum)
+print("")
+print("Note 4: The Way's 'type' parameter can be one the combination of these enumerated values:")
+print_enum(highway_enum)
+print("")
+print("Note 5: The Way's 'props' parameter can be the combination of these enumerated values:")
+print_enum(properties_enum)
+print("")
+print("Note 6: A value of zero for a Way's speed, weight, height, width or length means that there is no limit.")
+print("")
+print("Note 7: The Relation's 'except_transport' parameter can be the combination of these enumerated values:")
+print_enum(transports_enum)
+print("")
+
+
+import gc
+
+gc.collect()


=====================================
python/router.py
=====================================
@@ -0,0 +1,327 @@
+#!/usr/bin/python3
+##########################################
+# OSM router calling libroutino library from Python.
+#
+# Part of the Routino routing software.
+##########################################
+# This file Copyright 2018 Andrew M. Bishop
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+##########################################
+
+import argparse
+import sys
+import os
+import math
+
+import routino.router as routino
+
+
+# Parse the command line arguments
+
+argparser = argparse.ArgumentParser(description="Calculates a route using Routino and command line data.")
+
+argparser.add_argument("--version",          dest="version",       action='store_false',   help="Print the version of Routino.")
+
+argparser.add_argument("--dir",              dest="dirname",       type=str, default=None, help="The directory containing the routing database.")
+argparser.add_argument("--prefix",           dest="prefix",        type=str, default=None, help="The filename prefix for the routing database.")
+
+argparser.add_argument("--profiles",         dest="profiles",      type=str, default=None, help="The name of the XML file containing the profiles (defaults to 'profiles.xml' with '--dir' and '--prefix' options).")
+argparser.add_argument("--translations",     dest="translations",  type=str, default=None, help="The name of the XML file containing the translations (defaults to 'translations.xml' with '--dir' and '--prefix' options).")
+
+argparser.add_argument("--reverse",          dest="reverse",       action='store_true',    help="Find a route between the waypoints in reverse order.")
+argparser.add_argument("--loop",             dest="loop",          action='store_true',    help="Find a route that returns to the first waypoint.")
+
+argparser.add_argument("--output-html",      dest="html",          action='store_true',    help="Write an HTML description of the route.")
+argparser.add_argument("--output-gpx-track", dest="gpx_track",     action='store_true',    help="Write a GPX track file with all route points.")
+argparser.add_argument("--output-gpx-route", dest="gpx_route",     action='store_true',    help="Write a GPX route file with interesting junctions.")
+argparser.add_argument("--output-text",      dest="text",          action='store_true',    help="Write a plain text file with interesting junctions.")
+argparser.add_argument("--output-text-all",  dest="text_all",      action='store_true',    help="Write a plain text file with all route points.")
+argparser.add_argument("--output-none",      dest="none",          action='store_true',    help="Don't write any output files or read any translations. (If no output option is given then all are written.)")
+argparser.add_argument("--output-stdout",    dest="use_stdout",    action='store_true',    help="Write to stdout instead of a file (requires exactly one output format option, implies '--quiet').")
+
+argparser.add_argument("--list-html",        dest="list_html",     action='store_true',    help="Create an HTML list of the route.")
+argparser.add_argument("--list-html-all",    dest="list_html_all", action='store_true',    help="Create an HTML list of the route with all points.")
+argparser.add_argument("--list-text",        dest="list_text",     action='store_true',    help="Create a plain text list with interesting junctions.")
+argparser.add_argument("--list-text-all",    dest="list_text_all", action='store_true',    help="Create a plain text list with all route points.")
+
+argparser.add_argument("--profile",          dest="profilename",   type=str, default=None, help="Select the loaded profile with this name.")
+argparser.add_argument("--language",         dest="language",      type=str, default=None, help="Use the translations for specified language.")
+
+argparser.add_argument("--quickest",         dest="shortest",      action='store_false',   help="Find the quickest route between the waypoints.")
+argparser.add_argument("--shortest",         dest="shortest",      action='store_true',    help="Find the shortest route between the waypoints.")
+
+argparser.add_argument("--lon",              dest="lons",          action='append', type=float, help="Specify the longitude of the next waypoint (can also use '--lon<n>' to specify the n'th longitude).")
+argparser.add_argument("--lat",              dest="lats",          action='append', type=float, help="Specify the latitude of the next waypoint (can also use '--lat<n>' to specify the n'th latitude).")
+
+for i in range(1,99):
+    argparser.add_argument("--lon"+str(i),   dest="lon"+str(i),    type=float, help=argparse.SUPPRESS)
+    argparser.add_argument("--lat"+str(i),   dest="lat"+str(i),    type=float, help=argparse.SUPPRESS)
+
+args = argparser.parse_args()
+
+
+# Check the specified command line options
+
+if args.use_stdout and (int(args.html)+int(args.gpx_track)+int(args.gpx_route)+int(args.text)+int(args.text_all))!=1:
+    print("Error: The '--output-stdout' option requires exactly one other output option (but not '--output-none').")
+    sys.exit(1)
+
+if not args.html and not args.gpx_track and not args.gpx_route and not args.text and not args.text_all and not args.none:
+    args.html=True
+    args.gpx_track=True
+    args.gpx_route=True
+    args.text=True
+    args.text_all=True
+
+
+# Load in the selected profiles
+
+if args.profiles is not None:
+    if not os.access(args.profiles,os.F_OK):
+        print("Error: The '--profiles' option specifies a file '{:s}' that does not exist.".format(args.profiles))
+        sys.exit(1)
+else:
+    args.profiles=routino.FileName(args.dirname,args.prefix,"profiles.xml")
+
+    if not os.access(args.profiles,os.F_OK):
+        defaultprofiles = routino.FileName("../xml/","routino","profiles.xml")
+
+        if not os.access(defaultprofiles,os.F_OK):
+            print("Error: The '--profiles' option was not used and the files '{:s}' and '{:s}' do not exist.".format(args.profiles,defaultprofiles))
+            sys.exit(1)
+
+        args.profiles=defaultprofiles
+
+if args.profilename is None:
+    print("Error: A profile name must be specified")
+    sys.exit(1)
+
+if routino.ParseXMLProfiles(args.profiles):
+    print("Error: Cannot read the profiles in the file '{:s}'.".format(args.profiles))
+    sys.exit(1)
+
+profile=routino.GetProfile(args.profilename)
+
+if profile is None:
+    list = routino.GetProfileNames()
+
+    print("Error: Cannot find a profile called '{:s}' in the file '{:s}'.".format(args.profilename,args.profiles))
+    print("Profiles available are: {:s}.".format(", ".join(list)))
+    sys.exit(1)
+
+
+# Load in the selected translation
+
+if args.translations is not None:
+    if not os.access(args.translations,os.F_OK):
+        print("Error: The '--translations' option specifies a file '{:s}' that does not exist.".format(args.translations))
+        sys.exit(1)
+
+else:
+    args.translations=routino.FileName(args.dirname,args.prefix,"translations.xml")
+
+    if not os.access(translations,os.F_OK):
+        defaulttranslations = routino.FileName("../xml/","routino","translations.xml")
+
+        if not os.access(defaulttranslations,os.F_OK):
+            print("Error: The '--translations' option was not used and the files '{:s}' and '{:s}' do not exist.".format(args.translations,defaulttranslations))
+            sys.exit(1)
+
+        args.translations=defaulttranslations
+
+if routino.ParseXMLTranslations(args.translations):
+    print("Error: Cannot read the translations in the file '{:s}'.".format(args.translations))
+    sys.exit(1)
+
+if args.language is not None:
+    translation = routino.GetTranslation(args.language)
+
+    if translation is None:
+        list1 = routino.GetTranslationLanguages()
+        list2 = routino.GetTranslationLanguageFullNames()
+
+        print("Error: Cannot find a translation called '{:s}' in the file '{:s}'.".format(args.language,args.translations))
+        print("Languages available are: {:s}".format(", ".join([i1+" ("+i2+")" for i1,i2 in zip(list1,list2)])))
+        sys.exit(1)
+
+else:
+    translation = routino.GetTranslation("") # first in file
+
+    if translation is None:
+        print("Error: No translations in '{:s}'.".format(args.translations))
+        sys.exit(1)
+
+
+# Create the numbered waypoints
+
+firstlatlon = True
+
+for i in range(1,99):
+
+    lon = getattr(args,"lon"+str(i),None)
+    lat = getattr(args,"lat"+str(i),None)
+
+    if lon is None and lat is None:
+        continue
+
+    if lon is None or lat is None:
+        print("Error: All waypoints must have latitude and longitude.")
+        sys.exit(1)
+
+    if firstlatlon:
+        if args.lats is not None or args.lons is not None:
+            print("Error: Mixing numbered and un-numbered waypoints is not allowed.")
+            sys.exit(1)
+        else:
+            firstlatlon = False
+            args.lons = []
+            args.lats = []
+
+    args.lons.append(lon)
+    args.lats.append(lat)
+
+
+# Check the waypoints are valid
+
+if args.lats is None or len(args.lats) < 2 or args.lons is None or len(args.lons) < 2:
+    print("Error: At least two waypoints must be specified.")
+    sys.exit(1)
+
+if len(args.lats) != len(args.lons):
+    print("Error: Number of latitudes ({:d}) and longitudes ({:d}) do not match.".format(len(lats),len(lons)))
+    sys.exit(1)
+
+
+# Load in the routing database
+
+database = routino.LoadDatabase(args.dirname,args.prefix)
+
+if database is None:
+    print("Error: Could not load Routino database.")
+    sys.exit(1)
+
+
+# Check the profile is valid for use with this database
+
+if routino.ValidateProfile(database,profile)!=routino.ERROR_NONE:
+    print("Error: Profile is invalid or not compatible with database.")
+    sys.exit(1)
+
+
+# Loop through all waypoints
+
+nwaypoints = 0
+waypoints = []
+
+for n in range(len(args.lats)):
+
+    waypoint = routino.FindWaypoint(database, profile, args.lats[n], args.lons[n])
+
+    if waypoint is None:
+        print("Error: Cannot find node close to specified point {:d}.",n);
+        sys.exit(1)
+
+    waypoints.append(waypoint)
+
+
+# Create the route
+
+routing_options=0
+
+if args.shortest:
+    routing_options |= routino.ROUTE_SHORTEST
+else:
+    routing_options |= routino.ROUTE_QUICKEST
+
+if args.html     : routing_options |= routino.ROUTE_FILE_HTML
+if args.gpx_track: routing_options |= routino.ROUTE_FILE_GPX_TRACK
+if args.gpx_route: routing_options |= routino.ROUTE_FILE_GPX_ROUTE
+if args.text     : routing_options |= routino.ROUTE_FILE_TEXT
+if args.text_all : routing_options |= routino.ROUTE_FILE_TEXT_ALL
+
+if args.list_html    : routing_options |= routino.ROUTE_LIST_HTML
+if args.list_html_all: routing_options |= routino.ROUTE_LIST_HTML_ALL
+if args.list_text    : routing_options |= routino.ROUTE_LIST_TEXT
+if args.list_text_all: routing_options |= routino.ROUTE_LIST_TEXT_ALL
+
+if args.reverse: routing_options |= routino.ROUTE_REVERSE
+if args.loop   : routing_options |= routino.ROUTE_LOOP
+
+route = routino.CalculateRoute(database, profile, translation, waypoints, routing_options)
+
+if routino.errno >= routino.ERROR_NO_ROUTE_1:
+    print("Error: Cannot find a route between specified waypoints")
+    sys.exit(1)
+
+if routino.errno != routino.ERROR_NONE:
+    print("Error: Internal error ({:d}).".format(routino.errno))
+    sys.exit(1)
+
+
+# Print the list output
+
+if args.list_html or args.list_html_all or args.list_text or args.list_text_all:
+
+    list=route
+    first=True
+    last=False
+
+    while list:
+
+        if list.next:
+            last = False
+        else:
+            last = True
+
+        print("----------------")
+        print("Lon,Lat: {:.5f}, {:.5f}".format((180.0/math.pi)*list.lon,(180.0/math.pi)*list.lat))
+
+        if args.list_html or args.list_html_all or args.list_text or args.list_text_all:
+            print("Dist,Time: {:.3f} km, {:.1f} minutes".format(list.dist,list.time))
+
+        if args.list_text_all and not first:
+            print("Speed: {:0f} km/hr".format(list.speed))
+
+        print("Point type: {:d}".format(list.type))
+
+        if (args.list_html or args.list_html_all or args.list_text) and not first and not last:
+            print("Turn: {:d} degrees".format(list.turn))
+
+        if ((args.list_html or args.list_html_all or args.list_text) and not last) or (args.list_text_all and not first):
+            print("Bearing: {:d} degrees".format(list.bearing))
+
+        if ((args.list_html or args.list_text) and not last) or (args.list_html_all and list.name) or (args.list_text_all and not first):
+            print("Name: {:s}".format(list.name))
+
+        if args.list_html or (args.list_html_all and list.name):
+            print("Desc1: {:s}".format(list.desc1))
+            print("Desc2: {:s}".format(list.desc2))
+
+            if not last:
+                print("Desc3: {:s}".format(list.desc3))
+
+        list = list.next
+        first = False
+
+
+# Tidy up and exit
+
+routino.DeleteRoute(route)
+
+routino.UnloadDatabase(database)
+
+routino.FreeXMLProfiles()
+
+routino.FreeXMLTranslations()


=====================================
python/setup.py
=====================================
@@ -0,0 +1,55 @@
+# Python interface setup script
+#
+# Part of the Routino routing software.
+#
+# This file Copyright 2018 Andrew M. Bishop
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+import os
+import re
+from distutils.core import setup, Extension
+
+routino_router = Extension('routino._router',
+                           sources = ['src/_router.c'],
+                           include_dirs = ['../src'],
+                           library_dirs = ['../src'],
+                           libraries = ['routino'])
+
+# Note: the database needs access to all symbols, not just those
+# exported by the libroutino library so it must link with the object
+# files and not just the library.
+
+lib_files = []
+
+for file in os.listdir('../src'):
+    if re.search("-lib.o", file) and not re.search("-slim-lib.o", file):
+        lib_files.append("../src/" + file)
+
+routino_database = Extension('routino._database',
+                             sources = ['src/_database.cc', 'src/database.cc'],
+                             include_dirs = ['../src'],
+                             extra_objects = lib_files,
+                             library_dirs = ['../src'])
+
+setup (name = 'Routino',
+       version = '1.0',
+       author="Andrew M. Bishop", author_email='amb at routino.org',
+       url='http://routino.org/',
+       description = 'Interfaces to Routino in Python',
+       packages = ['routino'],
+       package_dir = {'routino': 'src'},
+       py_modules = ['routino', 'routino.router', 'routino.database'],
+       ext_modules = [routino_router, routino_database])


=====================================
python/src/__init__.py
=====================================


=====================================
python/src/database.cc
=====================================
@@ -0,0 +1,544 @@
+/***************************************
+ Routino database access from Python.
+
+ Part of the Routino routing software.
+ ******************/ /******************
+ This file Copyright 2018 Andrew M. Bishop
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ ***************************************/
+
+
+#include <vector>
+
+extern "C" {
+
+#include "types.h"
+
+#include "nodes.h"
+#include "segments.h"
+#include "ways.h"
+#include "relations.h"
+
+#include "routino.h"
+
+}
+
+#include "database.hh"
+
+
+/* Copied from ../src/routino.c */
+
+struct _Routino_Database
+{
+ Nodes      *nodes;
+ Segments   *segments;
+ Ways       *ways;
+ Relations  *relations;
+};
+
+
+/*++++++++++++++++++++++++++++++++++++++
+  Create the PythonDatabase object by loading the database and filling in some useful information.
+
+  PythonDatabase *LoadDatabase Return a pointer to the Python view of the database.
+
+  const char *dirname The name of the directory.
+
+  const char *prefix The filename prefix (or NULL).
+  ++++++++++++++++++++++++++++++++++++++*/
+
+PythonDatabase *LoadDatabase(const char *dirname,const char *prefix)
+{
+ Routino_Database *database = Routino_LoadDatabase(dirname, prefix);
+
+ if(!database)
+    return NULL;
+ else
+    return new PythonDatabase(dirname, prefix, database);
+}
+
+
+/*++++++++++++++++++++++++++++++++++++++
+  Create the PythonDatabase object by loading the database and filling in some useful information.
+
+  PythonDatabase LoadDatabase Return a pointer to the Python view of the database.
+
+  const char *dirname The name of the directory.
+  ++++++++++++++++++++++++++++++++++++++*/
+
+PythonDatabase *LoadDatabase(const char *dirname)
+{
+ return LoadDatabase(dirname,NULL);
+}
+
+
+/*++++++++++++++++++++++++++++++++++++++
+  Create the PythonDatabase by passing it a loaded database.
+
+  const char *_dirname The name of the directory.
+
+  const char *_prefix The filename prefix (or NULL).
+
+  Routino_Database *_database The opened database.
+  ++++++++++++++++++++++++++++++++++++++*/
+
+PythonDatabase::PythonDatabase(const char *_dirname,const char *_prefix, Routino_Database *_database)
+{
+ database = _database;
+
+ /* Copy the database path information */
+
+ dirname = new char[strlen(_dirname)+1];
+ strcpy(dirname,_dirname);
+
+ prefix = new char[strlen(_prefix)+1];
+ strcpy(prefix,_prefix);
+
+ /* Fill in the extra information */
+
+ nnodes     = database->segments->file.number;
+ nsegments  = database->nodes->file.number;
+ nways      = database->ways->file.number;
+ nrelations = database->relations->file.trnumber;
+}
+
+
+/*++++++++++++++++++++++++++++++++++++++
+  Destroy the PythonDatabase by unloading the database.
+  ++++++++++++++++++++++++++++++++++++++*/
+
+PythonDatabase::~PythonDatabase()
+{
+ Routino_UnloadDatabase(database);
+
+ delete[] dirname;
+ delete[] prefix;
+}
+
+
+/*++++++++++++++++++++++++++++++++++++++
+  Return a pointer to a modified Node data structure for use by Python.
+
+  PythonNode *GetNode Returns a pointer to the Python view of the node.
+
+  index_t item The index number of the Node.
+  ++++++++++++++++++++++++++++++++++++++*/
+
+PythonNode *PythonDatabase::GetNode(index_t item)
+{
+ PythonNode *pynode=new PythonNode(this);
+
+ Node *nodep=LookupNode(database->nodes,item,1);
+ double latitude,longitude;
+
+ GetLatLong(database->nodes,item,nodep,&latitude,&longitude);
+
+ pynode->id = item;
+
+ pynode->firstsegment = nodep->firstseg;
+
+ pynode->latitude = radians_to_degrees(latitude);
+ pynode->longitude = radians_to_degrees(longitude);
+
+ pynode->allow = nodep->allow;
+ pynode->flags = nodep->flags;
+
+ return pynode;
+}
+
+
+/*++++++++++++++++++++++++++++++++++++++
+  Return a pointer to a modified Segment data structure for use by Python.
+
+  PythonSegment *GetSegment Returs a pointer to the Python view of the segment.
+
+  index_t item The index number of the Segment.
+  ++++++++++++++++++++++++++++++++++++++*/
+
+PythonSegment *PythonDatabase::GetSegment(index_t item)
+{
+ PythonSegment *pysegment=new PythonSegment(this);
+
+ Segment *segmentp=LookupSegment(database->segments,item,1);
+
+ pysegment->id = item;
+
+ pysegment->node1 = segmentp->node1;
+ pysegment->node2 = segmentp->node2;
+
+ pysegment->next2 = segmentp->next2;
+
+ pysegment->way = segmentp->way;
+
+ pysegment->distance = distance_to_km(DISTANCE(segmentp->distance));
+
+ pysegment->flags = DISTFLAG(segmentp->distance);
+
+ return pysegment;
+}
+
+
+/*++++++++++++++++++++++++++++++++++++++
+  Return a pointer to a modified Way data structure for use by Python.
+
+  PythonWay *GetWay Returs a pointer to the Python view of the way.
+
+  index_t item The index number of the Way.
+  ++++++++++++++++++++++++++++++++++++++*/
+
+PythonWay *PythonDatabase::GetWay(index_t item)
+{
+ PythonWay *pyway=new PythonWay(this);
+
+ Way *wayp=LookupWay(database->ways,item,1);
+ char *name=WayName(database->ways,wayp);
+
+ pyway->id = item;
+
+ pyway->name = name;
+
+ pyway->allow = wayp->allow;
+
+ pyway->type = wayp->type;
+
+ pyway->props = wayp->props;
+
+ pyway->speed = speed_to_kph(wayp->speed);
+
+ pyway->weight = weight_to_tonnes(wayp->weight);
+ pyway->height = height_to_metres(wayp->height);
+ pyway->width  = width_to_metres(wayp->width);
+ pyway->length = length_to_metres(wayp->length);
+
+ return pyway;
+}
+
+
+/*++++++++++++++++++++++++++++++++++++++
+  Return a pointer to a modified Relation data structure for use by Python.
+
+  PythonRelation *GetRelation Returs a pointer to the Python view of the relation.
+
+  index_t item The index number of the Relation.
+  ++++++++++++++++++++++++++++++++++++++*/
+
+PythonRelation *PythonDatabase::GetRelation(index_t item)
+{
+ PythonRelation *pyrelation=new PythonRelation(this);
+
+ TurnRelation *relationp=LookupTurnRelation(database->relations,item,1);
+
+ pyrelation->id = item;
+
+ pyrelation->from_seg = relationp->from;
+ pyrelation->via_node = relationp->via;
+ pyrelation->to_seg   = relationp->to;
+
+ Node *nodep=LookupNode(database->nodes,relationp->via,1);
+ index_t from_way=NO_WAY,to_way=NO_WAY;
+ index_t from_node=NO_NODE,to_node=NO_NODE;
+
+ Segment *segmentp=FirstSegment(database->segments,nodep,1);
+
+ do
+   {
+    index_t seg=IndexSegment(database->segments,segmentp);
+
+    if(seg==relationp->from)
+      {
+       from_node=OtherNode(segmentp,relationp->via);
+       from_way=segmentp->way;
+      }
+
+    if(seg==relationp->to)
+      {
+       to_node=OtherNode(segmentp,relationp->via);
+       to_way=segmentp->way;
+      }
+
+    segmentp=NextSegment(database->segments,segmentp,relationp->via);
+   }
+ while(segmentp);
+
+ pyrelation->from_way = from_way;
+ pyrelation->to_way   = to_way;
+
+ pyrelation->from_node = from_node;
+ pyrelation->to_node   = to_node;
+
+ pyrelation->except_transport = relationp->except;
+
+ return pyrelation;
+}
+
+
+/*++++++++++++++++++++++++++++++++++++++
+  Create an iterator so that we can iterate through all nodes in the database.
+
+  PythonDatabaseIter<PythonNode> *PythonDatabase::Nodes Returns a pointer to a node iterator.
+  ++++++++++++++++++++++++++++++++++++++*/
+
+PythonDatabaseIter<PythonNode> *PythonDatabase::Nodes()
+{
+ return  new PythonDatabaseIter<PythonNode>(this,nnodes);
+}
+
+
+/*++++++++++++++++++++++++++++++++++++++
+  Create an iterator so that we can iterate through all segments in the database.
+
+  PythonDatabaseIter<PythonSegment> *PythonDatabase::Segments Returns a pointer to a segment iterator.
+  ++++++++++++++++++++++++++++++++++++++*/
+
+PythonDatabaseIter<PythonSegment> *PythonDatabase::Segments()
+{
+ return new PythonDatabaseIter<PythonSegment>(this,nsegments);
+}
+
+
+/*++++++++++++++++++++++++++++++++++++++
+  Create an iterator so that we can iterate through all ways in the database.
+
+  PythonDatabaseIter<PythonWay> *PythonDatabase::Ways Returns a pointer to a way iterator.
+  ++++++++++++++++++++++++++++++++++++++*/
+
+PythonDatabaseIter<PythonWay> *PythonDatabase::Ways()
+{
+ return new PythonDatabaseIter<PythonWay>(this,nways);
+}
+
+
+/*++++++++++++++++++++++++++++++++++++++
+  Create an iterator so that we can iterate through all relations in the database.
+
+  PythonDatabaseIter<PythonRelation> *PythonDatabase::Relations Returns a pointer to a relation iterator.
+  ++++++++++++++++++++++++++++++++++++++*/
+
+PythonDatabaseIter<PythonRelation> *PythonDatabase::Relations()
+{
+ return new PythonDatabaseIter<PythonRelation>(this,nrelations);
+}
+
+
+/*++++++++++++++++++++++++++++++++++++++
+  Fill in the segments array so that we can access all segments on the node.
+  ++++++++++++++++++++++++++++++++++++++*/
+
+void PythonNode::fill_segments()
+{
+ if(segments.size()==0)
+   {
+    Node *nodep=LookupNode(pydatabase->database->nodes,id,1);
+    Segment *segmentp=FirstSegment(pydatabase->database->segments,nodep,1);
+
+    do
+      {
+       index_t seg=IndexSegment(pydatabase->database->segments,segmentp);
+
+       segments.push_back(seg);
+
+       segmentp=NextSegment(pydatabase->database->segments,segmentp,id);
+      }
+    while(segmentp);
+   }
+}
+
+
+/*++++++++++++++++++++++++++++++++++++++
+  Create an iterator so that we can iterate through all segments on the node.
+
+  PythonNodeIter<PythonSegment> *PythonNode::Segments Returns a pointer to a segment iterator.
+  ++++++++++++++++++++++++++++++++++++++*/
+
+PythonNodeIter<PythonSegment> *PythonNode::Segments()
+{
+ fill_segments();
+
+ PythonNodeIter<PythonSegment> *pyiter=new PythonNodeIter<PythonSegment>(this,segments.size());
+
+ return pyiter;
+}
+
+
+/*++++++++++++++++++++++++++++++++++++++
+  Get a segment from the set of segments on the node.
+
+  PythonSegment *PythonNode::get_segment Returns a pointer to a segment.
+
+  index_t n The index of the segment.
+  ++++++++++++++++++++++++++++++++++++++*/
+
+PythonSegment *PythonNode::get_segment(index_t n)
+{
+ fill_segments();
+
+ if(n > segments.size())
+    return NULL;
+
+ return pydatabase->GetSegment(segments[n]);
+}
+
+
+/*++++++++++++++++++++++++++++++++++++++
+  When acting as a list return the selected item from the iterator.
+
+  template<> PythonNode *PythonDatabaseIter<PythonNode>::__getitem__ Returns a pointer to a node.
+
+  index_t n The index of the node.
+  ++++++++++++++++++++++++++++++++++++++*/
+
+template<> PythonNode *PythonDatabaseIter<PythonNode>::__getitem__(index_t n)
+{
+ return pydatabase->GetNode(n);
+}
+
+
+/*++++++++++++++++++++++++++++++++++++++
+  When acting as a list return the selected item from the iterator.
+
+  template<> PythonSegment *PythonDatabaseIter<PythonSegment>::__getitem__ Returns a pointer to a segment.
+
+  index_t n The index of the segment.
+  ++++++++++++++++++++++++++++++++++++++*/
+
+template<> PythonSegment *PythonDatabaseIter<PythonSegment>::__getitem__(index_t n)
+{
+ return pydatabase->GetSegment(n);
+}
+
+
+/*++++++++++++++++++++++++++++++++++++++
+  When acting as a list return the selected item from the iterator.
+
+  template<> PythonWay *PythonDatabaseIter<PythonWay>::__getitem__ Returns a pointer to a way.
+
+  index_t n The index of the way.
+  ++++++++++++++++++++++++++++++++++++++*/
+
+template<> PythonWay *PythonDatabaseIter<PythonWay>::__getitem__(index_t n)
+{
+ return pydatabase->GetWay(n);
+}
+
+
+/*++++++++++++++++++++++++++++++++++++++
+  When acting as a list return the selected item from the iterator.
+
+  template<> PythonRelation *PythonDatabaseIter<PythonRelation>::__getitem__ Returns a pointer to a relation.
+
+  index_t n The index of the relation.
+  ++++++++++++++++++++++++++++++++++++++*/
+
+template<> PythonRelation *PythonDatabaseIter<PythonRelation>::__getitem__(index_t n)
+{
+ return pydatabase->GetRelation(n);
+}
+
+
+/*++++++++++++++++++++++++++++++++++++++
+  When acting as a list return the selected item from the iterator.
+
+  template<> PythonSegment *PythonNodeIter<PythonSegment>::__getitem__ Returns a pointer to a segment.
+
+  index_t n The index of the segment.
+  ++++++++++++++++++++++++++++++++++++++*/
+
+template<> PythonSegment *PythonNodeIter<PythonSegment>::__getitem__(index_t n)
+{
+ return pynode->get_segment(n);
+}
+
+
+/*++++++++++++++++++++++++++++++++++++++
+  Convert a Python database to a viewable string.
+
+  char *PythonDatabase::__str__ Returns a pointer to a statically allocated string.
+  ++++++++++++++++++++++++++++++++++++++*/
+
+char *PythonDatabase::__str__()
+{
+ static char tmp[256];
+
+ if(prefix)
+    sprintf(tmp, "Database(%s,%s)", dirname, prefix);
+ else
+    sprintf(tmp, "Database(%s)", dirname);
+
+ return tmp;
+}
+
+
+/*++++++++++++++++++++++++++++++++++++++
+  Convert a Python node to a viewable string.
+
+  char *PythonNode::__str__ Returns a pointer to a statically allocated string.
+  ++++++++++++++++++++++++++++++++++++++*/
+
+char *PythonNode::__str__()
+{
+ static char tmp[64];
+
+ sprintf(tmp, "Node(%" Pindex_t ")", id);
+
+ return tmp;
+}
+
+
+/*++++++++++++++++++++++++++++++++++++++
+  Convert a Python segment to a viewable string.
+
+  char *PythonSegment::__str__ Returns a pointer to a statically allocated string.
+  ++++++++++++++++++++++++++++++++++++++*/
+
+char *PythonSegment::__str__()
+{
+ static char tmp[64];
+
+ sprintf(tmp, "Segment(%" Pindex_t ")", id);
+
+ return tmp;
+}
+
+
+/*++++++++++++++++++++++++++++++++++++++
+  Convert a Python way to a viewable string.
+
+  char *PythonWay::__str__ Returns a pointer to a statically allocated string.
+  ++++++++++++++++++++++++++++++++++++++*/
+
+char *PythonWay::__str__()
+{
+ static char tmp[64];
+
+ sprintf(tmp, "Way(%" Pindex_t ")", id);
+
+ return tmp;
+}
+
+
+/*++++++++++++++++++++++++++++++++++++++
+  Convert a Python relation to a viewable string.
+
+  char *PythonRelation::__str__ Returns a pointer to a statically allocated string.
+  ++++++++++++++++++++++++++++++++++++++*/
+
+char *PythonRelation::__str__()
+{
+ static char tmp[64];
+
+ sprintf(tmp, "Relation(%" Pindex_t ")", id);
+
+ return tmp;
+}


=====================================
python/src/database.hh
=====================================
@@ -0,0 +1,289 @@
+/***************************************
+ Header file for interface between Routino database and Python.
+
+ Part of the Routino routing software.
+ ******************/ /******************
+ This file Copyright 2018 Andrew M. Bishop
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ ***************************************/
+
+#ifndef DATABASE_H
+#define DATABASE_H    /*+ To stop multiple inclusions. +*/
+
+#include <vector>
+
+extern "C" {
+
+#include "types.h"
+
+#include "routino.h"
+
+}
+
+
+/* Constants that are not automatically picked up from types.h */
+
+const nodeflags_t Nodeflag_Super           = NODE_SUPER;
+const nodeflags_t Nodeflag_U_Turn          = NODE_UTURN;
+const nodeflags_t Nodeflag_Mini_Roundabout = NODE_MINIRNDBT;
+const nodeflags_t Nodeflag_Turn_Restrict   = NODE_TURNRSTRCT;
+const nodeflags_t Nodeflag_Turn_Restrict2  = NODE_TURNRSTRCT2;
+
+const distance_t Segmentflag_Area          = SEGMENT_AREA;
+const distance_t Segmentflag_Oneway_1to2   = ONEWAY_1TO2;
+const distance_t Segmentflag_Oneway_2to1   = ONEWAY_2TO1;
+const distance_t Segmentflag_Super         = SEGMENT_SUPER;
+const distance_t Segmentflag_Normal        = SEGMENT_NORMAL;
+
+
+/* Classes (much easier to use them than C for doing this with swig) */
+
+class PythonDatabase;
+
+class PythonNode;
+class PythonSegment;
+class PythonWay;
+class PythonRelation;
+
+template <class T> class PythonDatabaseIter;
+template <class T> class PythonNodeIter;
+
+
+/* The database as seen by Python */
+
+PythonDatabase *LoadDatabase(const char *dirname, const char *prefix);
+PythonDatabase *LoadDatabase(const char *dirname);
+
+class PythonDatabase
+{
+public:
+ PythonDatabase(const char *_dirname,const char *_prefix, Routino_Database* database); /*+ A constructor +*/
+ ~PythonDatabase();                                       /*+ A destructor to unload the database. +*/
+
+ PythonNode     *GetNode(index_t item);     /*+ Get a single node from the database. +*/
+ PythonSegment  *GetSegment(index_t item);  /*+ Get a single segment from the database. +*/
+ PythonWay      *GetWay(index_t item);      /*+ Get a single way from the database. +*/
+ PythonRelation *GetRelation(index_t item); /*+ Get a single relation from the database. +*/
+
+ PythonDatabaseIter<PythonNode>     *Nodes();     /*+ Create a node iterator to get all the nodes from the database. +*/
+ PythonDatabaseIter<PythonSegment>  *Segments();  /*+ Create a segment iterator to get all the segments from the database. +*/
+ PythonDatabaseIter<PythonWay>      *Ways();      /*+ Create a way iterator to get all the ways from the database. +*/
+ PythonDatabaseIter<PythonRelation> *Relations(); /*+ Create a relation iterator to get all the relations from the database. +*/
+
+ index_t nnodes;                /*+ The number of nodes in the database. +*/
+ index_t nsegments;             /*+ The number of segments in the database. +*/
+ index_t nways;                 /*+ The number of ways in the database. +*/
+ index_t nrelations;            /*+ The number of relations in the database. +*/
+
+ char          *__str__();      /*+ Convert the Python database to a string. +*/
+
+ friend class PythonNode;
+ friend class PythonSegment;
+ friend class PythonWay;
+ friend class PythonRelation;
+
+ private:
+
+ char             *dirname;     /*+ A copy of the database directory name. +*/
+ char             *prefix;      /*+ A copy of the database prefix. +*/
+
+ Routino_Database *database;    /*+ The database opened using the libroutino function. +*/
+};
+
+
+/* A node as seen by Python - copied from ../src/nodes.h and then modified */
+
+class PythonNode
+{
+public:
+ PythonNode(PythonDatabase* _pydatabase) { pydatabase = _pydatabase; } /*+ A constructor passed the database. +*/
+
+ index_t      id;               /*+ The index of this node. +*/
+
+ index_t      firstsegment;     /*+ The index of the first segment. +*/
+
+ PythonNodeIter<PythonSegment> *Segments();
+
+ double       latitude;         /*+ The node latitude in degrees. +*/
+ double       longitude;        /*+ The node longitude in degrees. +*/
+
+ transports_t allow;            /*+ The types of transport that are allowed through the node. +*/
+ nodeflags_t  flags;            /*+ Flags containing extra information (e.g. super-node, turn restriction). +*/
+
+ char         *__str__();       /*+ Convert the Python node to a string. +*/
+
+ private:
+
+ friend class PythonNodeIter<PythonSegment>;
+
+ PythonDatabase *pydatabase;    /*+ A pointer to the database that this node came from. +*/
+
+ std::vector<index_t> segments; /*+ The list of segments for this node, only filled in after calling Segments(). +*/
+
+ PythonSegment *get_segment(index_t item); /*+ Get a single segment from the node. +*/
+ void           fill_segments(); /*+ Fill in the list of segments. +*/
+};
+
+
+/* A segment as seen by Python - copied from ../src/segments.h and then modified */
+
+class PythonSegment
+{
+public:
+ PythonSegment(PythonDatabase* _pydatabase) { pydatabase = _pydatabase; } /*+ A constructor passed the database. +*/
+
+ index_t    id;                 /*+ The index of this segment. +*/
+
+ index_t    node1;              /*+ The index of the starting node. +*/
+ index_t    node2;              /*+ The index of the finishing node. +*/
+
+ PythonNode *Node1() { return pydatabase->GetNode(node1); }
+ PythonNode *Node2() { return pydatabase->GetNode(node2); }
+
+ index_t    next2;              /*+ The index of the next segment sharing node2. +*/
+
+ index_t    way;                /*+ The index of the way associated with the segment. +*/
+
+ PythonWay  *Way() { return pydatabase->GetWay(way); }
+
+ double     distance;           /*+ The distance between the nodes. +*/
+
+ distance_t flags;              /*+ The flags associated with the segment. +*/
+
+ char       *__str__();         /*+ Convert the Python segment to a string. +*/
+
+ private:
+
+ PythonDatabase *pydatabase;    /*+ A pointer to the database that this segment came from. +*/
+};
+
+
+/* A way as seen by Python - copied from ../src/ways.h and then modified */
+
+class PythonWay
+{
+public:
+ PythonWay(PythonDatabase* _pydatabase) { pydatabase = _pydatabase; } /*+ A constructor passed the database. +*/
+
+ index_t      id;               /*+ The index of this way. +*/
+
+ char         *name;            /*+ The offset of the name of the way in the names array. +*/
+
+ transports_t allow;            /*+ The type of traffic allowed on the way. +*/
+
+ highway_t    type;             /*+ The highway type of the way. +*/
+
+ properties_t props;            /*+ The properties of the way. +*/
+
+ double       speed;            /*+ The defined maximum speed limit of the way. +*/
+
+ double       weight;           /*+ The defined maximum weight of traffic on the way. +*/
+ double       height;           /*+ The defined maximum height of traffic on the way. +*/
+ double       width;            /*+ The defined maximum width of traffic on the way. +*/
+ double       length;           /*+ The defined maximum length of traffic on the way. +*/
+
+ char         *__str__();       /*+ Convert the Python way to a string. +*/
+
+ private:
+
+ PythonDatabase *pydatabase;    /*+ A pointer to the database that this segment came from. +*/
+};
+
+
+/* A relation as seen by Python - copied from ../src/relations.h and then modified */
+
+class PythonRelation
+{
+public:
+ PythonRelation(PythonDatabase* _pydatabase) { pydatabase = _pydatabase; } /*+ A constructor passed the database. +*/
+
+ index_t       id;              /*+ The index of this relation. +*/
+
+ index_t       from_seg;        /*+ The segment that the path comes from. +*/
+ index_t       via_node;        /*+ The node that the path goes via. +*/
+ index_t       to_seg;          /*+ The segment that the path goes to. +*/
+
+ PythonSegment *FromSegment() { return pydatabase->GetSegment(from_seg); }
+ PythonNode    *ViaNode()     { return pydatabase->GetNode(via_node); }
+ PythonSegment *ToSegment()   { return pydatabase->GetSegment(to_seg); }
+
+ index_t       from_way;        /*+ The way that the path comes from. +*/
+ index_t       to_way;          /*+ The way that the path goes to. +*/
+
+ PythonWay     *FromWay()     { return pydatabase->GetWay(from_way); }
+ PythonWay     *ToWay()       { return pydatabase->GetWay(to_way); }
+
+ index_t       from_node;       /*+ The node that the path comes from. +*/
+ index_t       to_node;         /*+ The node that the path goes to. +*/
+
+ PythonNode    *FromNode()    { return pydatabase->GetNode(from_node); }
+ PythonNode    *ToNode()      { return pydatabase->GetNode(to_node); }
+
+ transports_t  except_transport; /*+ The types of transports that that this relation does not apply to. +*/
+
+ char          *__str__();      /*+ Convert the Python relation to a string. +*/
+
+ private:
+
+ PythonDatabase *pydatabase;    /*+ A pointer to the database that this segment came from. +*/
+};
+
+
+/* A generic node/segment/way/relation iterator */
+
+template <class T> class PythonDatabaseIter
+{
+ public:
+
+ PythonDatabaseIter(PythonDatabase* _pydatabase, index_t _number) { pydatabase = _pydatabase; number = _number; } /*+ A constructor passed the database. +*/
+
+ index_t __len__() { return number; } /*+ When used as a list return the length of it. +*/
+ T      *__getitem__(index_t index);  /*+ When used as a list get a particular item from it. +*/
+
+ PythonDatabaseIter *__iter__() { return this; } /*+ When used as an iterator return itself. +*/
+ T                  *__next__() { if( next < number ) return __getitem__(next++); else return NULL; } /*+ When used as an iterator return the next item. +*/
+
+ private:
+
+ index_t next=0;                /*+ The next node/segment/way/relation to be returned. +*/
+ index_t number;                /*+ The number of nodes/segments/ways/relations in total. +*/
+
+ PythonDatabase *pydatabase;    /*+ A pointer to the database that this node/segment/way/relation came from. +*/
+};
+
+
+/* A segment iterator for nodes */
+
+template <class T> class PythonNodeIter
+{
+ public:
+
+ PythonNodeIter(PythonNode *_pynode, index_t _number) { pynode = _pynode; number = _number; } /*+ A constructor passed the node. +*/
+
+ index_t __len__() { return number; } /*+ When used as a list return the length of it. +*/
+ T      *__getitem__(index_t index);  /*+ When used as a list get a particular item from it. +*/
+
+ PythonNodeIter *__iter__() { return this; } /*+ When used as an iterator return itself. +*/
+ T              *__next__() { if( next < number ) return __getitem__(next++); else return NULL; } /*+ When used as an iterator return the next item. +*/
+
+ private:
+
+ index_t next=0;                /*+ The next segment to be returned. +*/
+ index_t number;                /*+ The number of segments in total. +*/
+
+ PythonNode *pynode;            /*+ A pointer to the node that these segments come from. +*/
+};
+
+#endif /* DATABASE_H */


=====================================
python/src/database.i
=====================================
@@ -0,0 +1,220 @@
+/***************************************
+ Python database interface definition.
+
+ Part of the Routino routing software.
+ ******************/ /******************
+ This file Copyright 2018 Andrew M. Bishop
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ ***************************************/
+
+
+/* Name the module 'database' in the 'routino' package */
+
+%module(package="routino.database") database
+
+
+/* Include the 'database.hh' header file in the auto-generated code */
+
+%{
+#include "database.hh"
+%}
+
+
+/* Typemaps for the special integer types used by Routino */
+
+%typemap(in)  index_t      { $1      = PyInt_AsLong($input); }
+%typemap(out) index_t      { $result = PyInt_FromLong($1);   }
+
+%typemap(in)  transport_t  { $1      = PyInt_AsLong($input); }
+%typemap(out) transport_t  { $result = PyInt_FromLong($1);   }
+
+%typemap(in)  transports_t { $1      = PyInt_AsLong($input); }
+%typemap(out) transports_t { $result = PyInt_FromLong($1);   }
+
+%typemap(in)  nodeflags_t  { $1      = PyInt_AsLong($input); }
+%typemap(out) nodeflags_t  { $result = PyInt_FromLong($1);   }
+
+%typemap(in)  highway_t    { $1      = PyInt_AsLong($input); }
+%typemap(out) highway_t    { $result = PyInt_FromLong($1);   }
+
+%typemap(in)  properties_t { $1      = PyInt_AsLong($input); }
+%typemap(out) properties_t { $result = PyInt_FromLong($1);   }
+
+%typemap(in)  distance_t   { $1      = PyInt_AsLong($input); }
+%typemap(out) distance_t   { $result = PyInt_FromLong($1);   }
+
+
+/* Exception handling for the iterators */
+
+%exception PythonDatabaseIter<PythonNode>::__next__ {
+  $action
+  if (!result)
+    {
+     PyErr_SetString(PyExc_StopIteration, "End of iterator");
+     return NULL;
+    }
+}
+
+%exception PythonDatabaseIter<PythonSegment>::__next__ {
+  $action
+  if (!result)
+    {
+     PyErr_SetString(PyExc_StopIteration, "End of iterator");
+     return NULL;
+    }
+}
+
+%exception PythonDatabaseIter<PythonWay>::__next__ {
+  $action
+  if (!result)
+    {
+     PyErr_SetString(PyExc_StopIteration, "End of iterator");
+     return NULL;
+    }
+}
+
+%exception PythonDatabaseIter<PythonRelation>::__next__ {
+  $action
+  if (!result)
+    {
+     PyErr_SetString(PyExc_StopIteration, "End of iterator");
+     return NULL;
+    }
+}
+
+%exception PythonNodeIter<PythonSegment>::__next__ {
+  $action
+  if (!result)
+    {
+     PyErr_SetString(PyExc_StopIteration, "End of iterator");
+     return NULL;
+    }
+}
+
+
+/* Rename the internal data types to remove the 'Python' prefix */
+
+%rename("Database") "PythonDatabase";
+
+%rename("Node")     "PythonNode";
+%rename("Segment")  "PythonSegment";
+%rename("Way")      "PythonWay";
+%rename("Relation") "PythonRelation";
+
+
+/* Ignore most of the constructors */
+
+%ignore PythonDatabase::PythonDatabase;
+
+%ignore PythonNode::PythonNode;
+%ignore PythonSegment::PythonSegment;
+%ignore PythonWay::PythonWay;
+%ignore PythonRelation::PythonRelation;
+
+%ignore PythonDatabaseIter<PythonNode>::PythonDatabaseIter;
+%ignore PythonDatabaseIter<PythonSegment>::PythonDatabaseIter;
+%ignore PythonDatabaseIter<PythonWay>::PythonDatabaseIter;
+%ignore PythonDatabaseIter<PythonRelation>::PythonDatabaseIter;
+
+%ignore PythonNodeIter<PythonSegment>::PythonNodeIter;
+
+
+/* Mark the functions that create new objects so they can be garbage collected */
+
+%newobject LoadDatabase;
+
+%newobject PythonDatabase::GetNode;
+%newobject PythonDatabase::GetSegment;
+%newobject PythonDatabase::GetWay;
+%newobject PythonDatabase::GetRelation;
+
+%newobject PythonDatabase::Nodes;
+%newobject PythonDatabase::Segments;
+%newobject PythonDatabase::Ways;
+%newobject PythonDatabase::Relations;
+
+%newobject PythonNode::Segments;
+
+%newobject PythonSegment::Node1;
+%newobject PythonSegment::Node2;
+%newobject PythonSegment::Way;
+
+%newobject PythonRelation::FromSegment;
+%newobject PythonRelation::ViaNode;
+%newobject PythonRelation::ToSegment;
+%newobject PythonRelation::FromWay;
+%newobject PythonRelation::ToWay;
+%newobject PythonRelation::FromNode;
+%newobject PythonRelation::ToNode;
+
+%newobject PythonDatabaseIter<PythonNode>::__getitem__;
+%newobject PythonDatabaseIter<PythonNode>::__next__;
+
+%newobject PythonDatabaseIter<PythonSegment>::__getitem__;
+%newobject PythonDatabaseIter<PythonSegment>::__next__;
+
+%newobject PythonDatabaseIter<PythonWay>::__getitem__;
+%newobject PythonDatabaseIter<PythonWay>::__next__;
+
+%newobject PythonDatabaseIter<PythonRelation>::__getitem__;
+%newobject PythonDatabaseIter<PythonRelation>::__next__;
+
+%newobject PythonNodeIter<PythonSegment>::__getitem__;
+%newobject PythonNodeIter<PythonSegment>::__next__;
+
+
+/* Ignore most things from the types.h file except the enumerations */
+
+%ignore M_PI;
+%ignore NWAYPOINTS;
+%ignore LAT_LONG_SCALE;
+%ignore LAT_LONG_BIN;
+
+%ignore kph_to_speed;
+%ignore tonnes_to_weight;
+%ignore metres_to_height;
+%ignore metres_to_width;
+%ignore metres_to_length;
+
+%ignore HighwayType;
+%ignore TransportType;
+%ignore PropertyType;
+%ignore HighwayName;
+%ignore TransportName;
+%ignore PropertyName;
+%ignore HighwaysNameList;
+%ignore AllowedNameList;
+%ignore PropertiesNameList;
+%ignore HighwayList;
+%ignore TransportList;
+%ignore PropertyList;
+
+
+/* Use the 'database.hh' header file to generate the wrapper (everything is read-only) */
+
+%immutable;
+
+%include "database.hh"
+%include "../src/types.h"
+
+
+/* Declare the specific templates */
+
+%template(DatabaseNodeIter)     PythonDatabaseIter<PythonNode>;
+%template(DatabaseSegmentIter)  PythonDatabaseIter<PythonSegment>;
+%template(DatabaseWayIter)      PythonDatabaseIter<PythonWay>;
+%template(DatabaseRelationIter) PythonDatabaseIter<PythonRelation>;
+
+%template(NodeSegmentIter)  PythonNodeIter<PythonSegment>;


=====================================
python/src/router.i
=====================================
@@ -0,0 +1,127 @@
+/***************************************
+ Python router interface definition.
+
+ Part of the Routino routing software.
+ ******************/ /******************
+ This file Copyright 2018 Andrew M. Bishop
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ ***************************************/
+
+
+/* Name the module 'router' in the 'routino' package */
+
+%module(package="routino.router") router
+
+
+/* Include the 'routino.h' header file from the library in the auto-generated code */
+
+%{
+#include "routino.h"
+%}
+
+
+/* Return NULL-terminated arrays of strings as a list of strings */
+
+%typemap(ret) char** {
+  $result = PyList_New(0);
+
+  char **p=$1;
+
+  while(*p)
+    {
+     PyList_Append($result, PyString_FromString(*p));
+     p++;
+    }
+}
+
+
+/* Handle lists of Routino Waypoints as an array */
+
+%typemap(in) Routino_Waypoint ** {
+  /* Check if is a list */
+  if (PyList_Check($input))
+    {
+     int size = PyList_Size($input);
+     int i = 0;
+     $1 = (Routino_Waypoint **) malloc(size*sizeof(Routino_Waypoint *));
+     for (i = 0; i < size; i++)
+        if (!SWIG_IsOK(SWIG_ConvertPtr(PyList_GetItem($input, i), (void **) &$1[i], $descriptor(Routino_Waypoint*), 0)))
+           SWIG_exception_fail(SWIG_TypeError, "in method '$symname', expecting type Routino_Waypoint");
+  } else {
+    PyErr_SetString(PyExc_TypeError, "not a list");
+    SWIG_fail;
+  }
+}
+
+%typemap(freearg) Routino_Waypoint ** {
+  free((Routino_Waypoint *) $1);
+}
+
+
+/* Rename variables and functions by stripping 'Routino_' or 'ROUTINO_' prefixes */
+
+%rename("%(regex:/R[Oo][Uu][Tt][Ii][Nn][Oo]_(.*)/\\1/)s") "";
+
+/* Rename the Routino_CalculateRoute() function so we can replace with a Python wrapper */
+
+%rename("_CalculateRoute") "Routino_CalculateRoute";
+
+/* Rename the Routino_LoadDatabase() function so we can replace with a Python wrapper */
+
+%rename("_LoadDatabase") "Routino_LoadDatabase";
+
+
+/* Add some custom Python code to the module */
+
+%pythoncode %{
+
+# Set up a replacement function for a macro in the original
+
+def CheckAPIVersion():
+    return _router.Check_API_Version(_router.API_VERSION)
+
+# Set up a replacement function so that we do not need to pass the size of the list
+
+def CalculateRoute(database, profile, translation, waypoints, options, progress=None):
+    return _router._CalculateRoute(database, profile, translation, waypoints, len(waypoints), options, progress)
+
+# Set up a replacement function to make the second argument optional
+
+def LoadDatabase(dirname, prefix=None):
+    return _router._LoadDatabase(dirname, prefix)
+
+# Create a function for concatenating directory names, prefixes and filenames
+
+def FileName(dirname, prefix, name):
+
+    filename=""
+
+    if dirname is not None:
+        filename=dirname + "/"
+
+    if prefix is not None:
+        filename += prefix + "-"
+
+    filename += name
+
+    return filename
+%}
+
+
+/* Use the 'routino.h' header file from the library to generate the wrapper (everything is read-only) */
+
+%immutable;
+
+%include "../src/routino.h"


=====================================
python/test/Makefile
=====================================
@@ -0,0 +1,63 @@
+# Test cases Makefile
+#
+# Part of the Routino routing software.
+#
+# This file Copyright 2011-2015, 2018 Andrew M. Bishop
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# All configuration is in the top-level Makefile.conf
+
+include ../../Makefile.conf
+
+TESTDIR=../../src/test
+
+# Executables
+
+EXE=$(TESTDIR)/is-fast-math$(.EXE)
+
+########
+
+all:
+
+########
+
+test: $(EXE)
+	@./run-router-tests.sh
+	@./run-database-tests.sh
+
+########
+
+$(EXE):
+	cd $(TESTDIR) && $(MAKE) test
+
+########
+
+install:
+
+########
+
+clean:
+	rm -rf results
+	rm -f *.log
+	rm -f *~
+
+########
+
+distclean: clean
+
+########
+
+.PHONY:: all test install clean distclean


=====================================
python/test/run-database-tests.sh
=====================================
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+# Python build location
+
+PYTHONPATH=`echo ../build/lib.*`
+export PYTHONPATH
+
+# Run the test
+
+python3 ../database.py
+
+# Finish
+
+exit 0


=====================================
python/test/run-one-test.sh
=====================================
@@ -0,0 +1,100 @@
+#!/bin/sh
+
+# Main tests directory
+
+testdir=../../src/test
+
+# Exit on error
+
+set -e
+
+# Test name
+
+name=`basename $1 .sh`
+
+# Libroutino location
+
+LD_LIBRARY_PATH=$testdir/..:$LD_LIBRARY_PATH
+export LD_LIBRARY_PATH
+
+# Python build location
+
+PYTHONPATH=`echo ../build/lib.*`
+export PYTHONPATH
+
+# Create the output directory
+
+dir=results
+
+[ -d $dir ] || mkdir $dir
+
+# Name related options
+
+osm=$testdir/$name.osm
+log=$name.log
+
+option_prefix="--prefix=$name"
+option_dir="--dir=$testdir/fat"
+
+# Generic program options
+
+option_router="--profile=motorcar --profiles=../../xml/routino-profiles.xml --translations=$testdir/copyright.xml"
+
+
+# Run waypoints program
+
+run_waypoints()
+{
+    perl $testdir/waypoints.pl $@
+}
+
+
+# Run planetsplitter
+
+run_planetsplitter()
+{
+    echo "Skipping planetsplitter"
+}
+
+
+# Run filedumper
+
+run_filedumper()
+{
+    echo "Skipping filedumper"
+}
+
+
+# Run the router
+
+run_router()
+{
+    waypoint=$1
+
+    shift
+
+    [ -d $dir/$name-$waypoint ] || mkdir $dir/$name-$waypoint
+
+    echo ../router.py $option_dir $option_prefix $option_osm $option_router $@ >> $log
+         ../router.py $option_dir $option_prefix $option_osm $option_router $@ >> $log
+
+    mv shortest* $dir/$name-$waypoint
+
+    echo diff -u $testdir/expected/$name-$waypoint.txt $dir/$name-$waypoint/shortest-all.txt >> $log
+
+    if $testdir/is-fast-math; then
+        diff -U 0 $testdir/expected/$name-$waypoint.txt $dir/$name-$waypoint/shortest-all.txt | 2>&1 egrep '^[-+] ' || true
+    else
+        diff -u $testdir/expected/$name-$waypoint.txt $dir/$name-$waypoint/shortest-all.txt >> $log
+    fi
+}
+
+
+# Run the specific test script
+
+. $testdir/$name.sh
+
+
+# Finish
+
+exit 0


=====================================
python/test/run-router-tests.sh
=====================================
@@ -0,0 +1,68 @@
+#!/bin/sh
+
+# Main tests directory
+
+testdir=../../src/test
+
+# Overall status
+
+status=true
+
+# Functions for running tests
+
+run_a_test ()
+{
+    script=$1
+    shift
+
+    if ./run-one-test.sh $script $@ ; then
+        echo "... passed"
+    else
+        echo "... FAILED"
+        status=false
+    fi
+}
+
+compare_results ()
+{
+    if diff -q -r $1 $2; then
+        echo "... matched"
+    else
+        echo "... match FAILED"
+        status=false
+    fi
+}
+
+
+# Initial informational message
+
+echo ""
+$testdir/is-fast-math message
+
+
+# Get the list of tests
+
+scripts=`echo $testdir/*.osm | sed -e s/.osm/.sh/g`
+
+# Run the scripts
+
+for script in $scripts; do
+    echo ""
+    echo "Testing: $script ... "
+    run_a_test $script
+done
+
+
+# Check results
+
+if $status; then
+    echo "Success: all tests passed"
+else
+    echo "Warning: Some tests FAILED"
+    exit 1
+fi
+
+
+# Finish
+
+exit 0



View it on GitLab: https://salsa.debian.org/debian-gis-team/routino/compare/c335b24e0489627e291822986b47b52706166fcd...fd0b60b5338c42c13bcca1436e3d4ee2c5589474

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/routino/compare/c335b24e0489627e291822986b47b52706166fcd...fd0b60b5338c42c13bcca1436e3d4ee2c5589474
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20190909/6df043c0/attachment-0001.html>


More information about the Pkg-grass-devel mailing list