Skip to content

Commit

Permalink
Add QGIS plugin.
Browse files Browse the repository at this point in the history
  • Loading branch information
rkistner committed May 11, 2013
1 parent 39299b8 commit 2bdb793
Show file tree
Hide file tree
Showing 10 changed files with 195 additions and 4 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Expand Up @@ -6,3 +6,6 @@
*.pyc
*.png
*.osm
*.egg

resources.py
2 changes: 1 addition & 1 deletion Readme.md
Expand Up @@ -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:

Expand Down
35 changes: 35 additions & 0 deletions __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)
2 changes: 2 additions & 0 deletions bundle.sh
@@ -0,0 +1,2 @@
#!/bin/sh
zip chinesepostman *.py lib/*.egg metadata.txt icon.png
122 changes: 122 additions & 0 deletions 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

3 changes: 3 additions & 0 deletions install.sh
@@ -0,0 +1,3 @@
#!/bin/sh
pyrcc4 -o resources.py resources.qrc
ln -sfn $PWD $HOME/.qgis/python/plugins/chinesepostman
Empty file added lib/.gitkeep
Empty file.
17 changes: 17 additions & 0 deletions 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
10 changes: 7 additions & 3 deletions 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
Expand Down Expand Up @@ -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)

5 changes: 5 additions & 0 deletions resources.qrc
@@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/plugins/chinesepostman" >
<file>icon.png</file>
</qresource>
</RCC>

0 comments on commit 2bdb793

Please sign in to comment.