From 2bdb793e319522a2b4976ce51ec38b95117fafc6 Mon Sep 17 00:00:00 2001 From: Ralf Kistner Date: Sat, 11 May 2013 16:50:13 +0200 Subject: [PATCH] Add QGIS plugin. --- .gitignore | 3 ++ Readme.md | 2 +- __init__.py | 35 +++++++++++++ bundle.sh | 2 + chinesepostman.py | 122 ++++++++++++++++++++++++++++++++++++++++++++++ install.sh | 3 ++ lib/.gitkeep | 0 metadata.txt | 17 +++++++ postman.py | 10 ++-- resources.qrc | 5 ++ 10 files changed, 195 insertions(+), 4 deletions(-) create mode 100644 __init__.py create mode 100755 bundle.sh create mode 100644 chinesepostman.py create mode 100755 install.sh create mode 100644 lib/.gitkeep create mode 100644 metadata.txt create mode 100644 resources.qrc diff --git a/.gitignore b/.gitignore index 72231df..9e548f5 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ *.pyc *.png *.osm +*.egg + +resources.py diff --git a/Readme.md b/Readme.md index aab7ae5..82304e2 100644 --- a/Readme.md +++ b/Readme.md @@ -7,7 +7,7 @@ This is a Python program to solve the [Chinese postman problem](http://en.wikipe * Python2.7 * [networkx](http://networkx.lanl.gov/) -## Usage +## Command-line Usage Input data file must be in CSV format, with each row containing the following columns: diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..c71e044 --- /dev/null +++ b/__init__.py @@ -0,0 +1,35 @@ +""" +/*************************************************************************** + ChinesePostman + A QGIS plugin + Chinese Postman Solver + ------------------- + begin : 2013-05-11 + copyright : (C) 2013 by Ralf Kistner + email : ralf.kistner@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 script initializes the plugin, making it known to QGIS. +""" +def name(): + return "Chinese Postman Solver" +def description(): + return "Chinese Postman Solver" +def version(): + return "Version 0.1" +def icon(): + return "icon.png" +def qgisMinimumVersion(): + return "1.7" +def classFactory(iface): + # load ChinesePostman class from file ChinesePostman + from chinesepostman import ChinesePostman + return ChinesePostman(iface) diff --git a/bundle.sh b/bundle.sh new file mode 100755 index 0000000..dd04aac --- /dev/null +++ b/bundle.sh @@ -0,0 +1,2 @@ +#!/bin/sh +zip chinesepostman *.py lib/*.egg metadata.txt icon.png diff --git a/chinesepostman.py b/chinesepostman.py new file mode 100644 index 0000000..1506b76 --- /dev/null +++ b/chinesepostman.py @@ -0,0 +1,122 @@ +""" +/*************************************************************************** + ChinesePostman + A QGIS plugin + Chinese Postman Solver + ------------------- + begin : 2013-05-11 + copyright : (C) 2013 by Ralf Kistner + email : ralf.kistner@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ +""" + +# To reload this plugin after modifying, run: +# qgis.utils.reloadPlugin('chinesepostman') + +import postman + +# Import the PyQt and QGIS libraries +from PyQt4.QtCore import * +from PyQt4.QtGui import * +from qgis.core import * +import networkx as nx + +# We need to import resources, even though we don't use it directly +import resources + +class ChinesePostman: + + def __init__(self, iface): + # Save reference to the QGIS interface + self.iface = iface + + def initGui(self): + # Create action that will start plugin configuration + self.action = QAction(QIcon(":/plugins/chinesepostman/icon.png"), \ + "Chinese Postman", self.iface.mainWindow()) + # connect the action to the run method + QObject.connect(self.action, SIGNAL("triggered()"), self.run) + + # Add toolbar button and menu item + self.iface.addToolBarIcon(self.action) + self.iface.addPluginToMenu("&Chinese Postman", self.action) + + def unload(self): + # Remove the plugin menu item and icon + self.iface.removePluginMenu("&Chinese Postman",self.action) + self.iface.removeToolBarIcon(self.action) + + # run method that performs all the real work + def run(self): + layer = self.iface.mapCanvas().currentLayer() + + graph = build_graph(layer.selectedFeatures()) + graph = postman.validate_graph(graph) + + paths = postman.chinese_postman_paths(graph, n=1) + + for eulerian_graph, nodes in paths: + + in_length = postman.edge_sum(graph)/1000.0 + path_length = postman.edge_sum(eulerian_graph)/1000.0 + duplicate_length = path_length - in_length + + info = "" + info += "Total length of roads: %.3f km\n" % in_length + info += "Total length of path: %.3f km\n" % path_length + info += "Length of sections visited twice: %.3f km\n" % duplicate_length + + QMessageBox.information(None, "Chinese Postman", "Done:\n%s" % info) + newlayer = build_layer(eulerian_graph, nodes, layer.crs()) + QgsMapLayerRegistry.instance().addMapLayer(newlayer) + + +def build_layer(graph, nodes, crs): + # create layer + + # We want to set the CRS without prompting the user, so we disable prompting first + s = QSettings() + oldValidation = s.value("/Projections/defaultBehaviour", "useGlobal").toString() + s.setValue("/Projections/defaultBehaviour", "useGlobal") + + vl = QgsVectorLayer("LineString", "chinese_postman", "memory") + vl.setCrs(crs) + + s.setValue("/Projections/defaultBehaviour", oldValidation) + + pr = vl.dataProvider() + + # We use a single polyline to represent the route + points = [] + for node in nodes: + points.append(QgsPoint(node[0], node[1])) + + # add the feature + fet = QgsFeature() + fet.setGeometry(QgsGeometry.fromPolyline(points)) + + pr.addFeatures([fet]) + + # update layer's extent when new features have been added + # because change of extent in provider is not propagated to the layer + vl.updateExtents() + return vl + +def build_graph(features): + graph = nx.Graph() + for feature in features: + geom = feature.geometry() + nodes = geom.asPolyline() + for start, end in postman.pairs(nodes): + graph.add_edge((start[0], start[1]), (end[0], end[1]), weight=geom.length()) + return graph + diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..4e2df6e --- /dev/null +++ b/install.sh @@ -0,0 +1,3 @@ +#!/bin/sh +pyrcc4 -o resources.py resources.qrc +ln -sfn $PWD $HOME/.qgis/python/plugins/chinesepostman diff --git a/lib/.gitkeep b/lib/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/metadata.txt b/metadata.txt new file mode 100644 index 0000000..50537b9 --- /dev/null +++ b/metadata.txt @@ -0,0 +1,17 @@ +# This file contains metadata for your plugin. Beginning +# with version 1.8 this is the preferred way to supply information about a +# plugin. The current method of embedding metadata in __init__.py will +# be supported until version 2.0 + +# This file should be included when you package your plugin. + +[general] +name=Chinese Postman Solver +description=Chinese Postman Solver +version=0.1 +qgisMinimumVersion=1.7 +class_name=ChinesePostman +website= +[author] +name=Ralf Kistner +email=ralf.kistner@gmail.com diff --git a/postman.py b/postman.py index d937311..114a23e 100755 --- a/postman.py +++ b/postman.py @@ -1,14 +1,19 @@ #!/usr/bin/env python2.7 from __future__ import print_function import os -from tempfile import mkstemp +import sys import tempfile import subprocess __author__ = 'Ralf Kistner' +# Use the bundled networkx egg +if __name__ == '__main__': + nxegg = 'lib/networkx-1.7-py2.7.egg' +else: + nxegg = os.path.join(os.path.dirname(__file__), 'lib/networkx-1.7-py2.7.egg') +sys.path.append(nxegg) -import sys import csv import networkx as nx import xml.dom.minidom as minidom @@ -317,4 +322,3 @@ def chinese_postman_paths(graph, n=5): if args.png: specify_positions(eulerian_graph) make_png(eulerian_graph, args.png.name) - diff --git a/resources.qrc b/resources.qrc new file mode 100644 index 0000000..cb67a47 --- /dev/null +++ b/resources.qrc @@ -0,0 +1,5 @@ + + + icon.png + +