[Thuban-commits] r2846 - in trunk/thuban/Extensions: . importMP importMP/test
scm-commit@wald.intevation.org
scm-commit at wald.intevation.org
Fri Jun 20 07:27:41 CEST 2008
Author: elachuni
Date: 2008-06-20 07:27:40 +0200 (Fri, 20 Jun 2008)
New Revision: 2846
Added:
trunk/thuban/Extensions/importMP/
trunk/thuban/Extensions/importMP/__init__.py
trunk/thuban/Extensions/importMP/importMP.py
trunk/thuban/Extensions/importMP/pfm.py
trunk/thuban/Extensions/importMP/test/
trunk/thuban/Extensions/importMP/test/snippets.py
trunk/thuban/Extensions/importMP/test/test_pfm.py
Log:
Adding a Polish Map File importer extension
Added: trunk/thuban/Extensions/importMP/__init__.py
===================================================================
--- trunk/thuban/Extensions/importMP/__init__.py 2008-06-20 05:20:03 UTC (rev 2845)
+++ trunk/thuban/Extensions/importMP/__init__.py 2008-06-20 05:27:40 UTC (rev 2846)
@@ -0,0 +1,22 @@
+# Copyright (c) 2008 by Intevation GmbH
+# Authors:
+# Anthony Lenton <anthony at except.com.ar>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with Thuban for details.
+
+# import the actual module
+import importMP
+
+# perform the registration of the extension
+from Thuban import _
+from Thuban.UI.extensionregistry import ExtensionDesc, ext_registry
+
+ext_registry.add(ExtensionDesc(
+ name = 'importMP',
+ version = '0.1.1',
+ authors= [ 'Anthony Lenton' ],
+ copyright = '2008 by Intevation GmbH',
+ desc = _("Import a Polish Map File (.mp)\n" \
+ "and convert it to Thuban.")))
+
Added: trunk/thuban/Extensions/importMP/importMP.py
===================================================================
--- trunk/thuban/Extensions/importMP/importMP.py 2008-06-20 05:20:03 UTC (rev 2845)
+++ trunk/thuban/Extensions/importMP/importMP.py 2008-06-20 05:27:40 UTC (rev 2846)
@@ -0,0 +1,284 @@
+import os
+import wx
+
+if __name__ != '__main__':
+ from Thuban.UI.command import registry, Command
+ from Thuban import _
+ from Thuban.UI.mainwindow import main_menu
+ from Thuban.UI import internal_from_wxstring
+
+from Thuban.Model.layer import Layer
+from Thuban.Model.classification import Classification, ClassGroupProperties, ClassGroupSingleton
+from Thuban.Model.color import Color
+import shapelib
+import dbflib
+from pfm import MPParser
+
+def pfm2shp (src_fname, dest_fname):
+ """Convert a file from Polish Map File into Shapefiles. """
+ fp = open(src_fname)
+ parser = MPParser()
+ parser.parse (fp)
+
+ poi_shp_filename = dest_fname + '_poi.shp'
+ poi_dbf_filename = dest_fname + '_poi.dbf'
+ poi_shp = shapelib.create(poi_shp_filename, shapelib.SHPT_POINT)
+ poi_dbf = dbflib.create(poi_dbf_filename)
+ poi_dbf.add_field('Label', dbflib.FTString, 80, 0)
+ del poi_dbf
+ poi_dbf = dbflib.open(poi_dbf_filename, 'r+b')
+ i = 0
+ for poi in parser.pois:
+ obj = shapelib.SHPObject(shapelib.SHPT_POINT, i, [[(poi['long'], poi['lat'])]])
+ poi_shp.write_object(-1, obj)
+ poi_dbf.write_record(i, { 'Label': poi.get('Label', '') })
+ i += 1
+ del poi_shp
+ del poi_dbf
+
+ polygon_shp_filename = dest_fname + '_polygon.shp'
+ polygon_dbf_filename = dest_fname + '_polygon.dbf'
+ polygon_shp = shapelib.create(polygon_shp_filename, shapelib.SHPT_POLYGON)
+ polygon_dbf = dbflib.create(polygon_dbf_filename)
+ polygon_dbf.add_field('Label', dbflib.FTString, 80, 0)
+ polygon_dbf.add_field('Type', dbflib.FTString, 80, 0)
+ del polygon_dbf
+ polygon_dbf = dbflib.open(polygon_dbf_filename, 'r+b')
+ i = 0
+ for poly in parser.polygons:
+ if poly['Type'].lower() == '0x4b': # Skip background
+ continue
+ obj = shapelib.SHPObject(shapelib.SHPT_POLYGON, i, [zip (poly['long'], poly['lat'])])
+ polygon_shp.write_object(-1, obj)
+ polygon_dbf.write_record(i, {'Label': poly.get('Label', ''),
+ 'Type': poly.get('parsedType', '')})
+ i += 1
+ del polygon_shp
+ del polygon_dbf
+
+ bg_shp_filename = dest_fname + '_bg.shp'
+ bg_dbf_filename = dest_fname + '_bg.dbf'
+ bg_shp = shapelib.create(bg_shp_filename, shapelib.SHPT_POLYGON)
+ bg_dbf = dbflib.create(bg_dbf_filename)
+ bg_dbf.add_field('Label', dbflib.FTString, 80, 0)
+ bg_dbf.add_field('Type', dbflib.FTString, 8, 0)
+ del bg_dbf
+ bg_dbf = dbflib.open(bg_dbf_filename, 'r+b')
+ i = 0
+ for poly in parser.polygons:
+ if poly['Type'].lower() == '0x4b': # Process background
+ obj = shapelib.SHPObject(shapelib.SHPT_POLYGON, i, [zip (poly['long'], poly['lat'])])
+ bg_shp.write_object(-1, obj)
+ bg_dbf.write_record(i, {'Label': poly.get('Label', ''),
+ 'Type': poly.get('Type', '')})
+ i += 1
+ del bg_shp
+ del bg_dbf
+
+ road_shp_filename = dest_fname + '_road.shp'
+ road_dbf_filename = dest_fname + '_road.dbf'
+ road_shp = shapelib.create(road_shp_filename, shapelib.SHPT_ARC)
+ road_dbf = dbflib.create(road_dbf_filename)
+ road_dbf.add_field('Label', dbflib.FTString, 80, 0)
+ road_dbf.add_field('Type', dbflib.FTString, 80, 0)
+ del road_dbf
+ road_dbf = dbflib.open(road_dbf_filename, 'r+b')
+ i = 0
+ for poly in parser.polylines:
+ obj = shapelib.SHPObject(shapelib.SHPT_ARC, i, [zip (poly['long'], poly['lat'])])
+ road_shp.write_object(-1, obj)
+ road_dbf.write_record(i, {'Label': poly.get('Label', ''),
+ 'Type': poly.get('parsedType', '')})
+ i += 1
+ del road_shp
+ del road_dbf
+ return parser
+
+def getPolygonClassification(polygons):
+ clazz = Classification()
+ waterProps = ClassGroupProperties()
+ waterProps.SetFill (Color (0.7, 0.7, 1))
+ vegetProps = ClassGroupProperties()
+ vegetProps.SetFill (Color (0.7, 1, 0.7))
+ buildProps = ClassGroupProperties()
+ buildProps.SetFill (Color (0.6, 0.6, 0.6))
+ iceProps = ClassGroupProperties()
+ iceProps.SetFill (Color (1, 1, 1))
+ earthProps = ClassGroupProperties()
+ earthProps.SetFill (Color (0.9, 0.6, 0.3))
+
+ typeProps = {'Parking Garage': buildProps, 'City': buildProps,
+ 'Car Park (Parking Lot)': buildProps, 'Glacier': iceProps,
+ 'University': buildProps, 'Hospital': buildProps,
+ 'Cemetery': buildProps, 'City Park': vegetProps,
+ 'National park': vegetProps, 'Reservation': vegetProps,
+ 'Depth area - blue 4': waterProps,
+ 'Depth area - blue 5': waterProps,
+ 'Depth area - blue 2': waterProps,
+ 'Depth area - blue 3': waterProps,
+ 'Depth area - blue 1': waterProps, 'Tundra': iceProps,
+ 'Man made area': buildProps, 'Industrial': buildProps,
+ 'Airport Runway': buildProps, 'Land - urban': buildProps,
+ 'Military': buildProps, 'Intermittent River/Lake': waterProps,
+ 'Lake': waterProps, 'Depth area - white 1': waterProps,
+ 'Airport': buildProps, 'State Park': vegetProps,
+ 'Land - non-urban': earthProps, 'Ocean': waterProps,
+ 'Scrub': vegetProps, 'River': waterProps, 'Bridge': buildProps,
+ 'Golf': vegetProps, 'Depth area - white': waterProps,
+ 'Marina': waterProps, 'Blue-Unknown': waterProps,
+ 'Orchard or plantation': vegetProps, 'Flats': earthProps,
+ 'Woods': vegetProps, 'Wetland': earthProps, 'Sea': waterProps,
+ 'Shopping Centre': buildProps, 'Land - white': iceProps,
+ 'Sport': buildProps}
+
+ types = list(set([p['parsedType'] for p in polygons]))
+ for t in types:
+ if t in typeProps.keys():
+ group = ClassGroupSingleton (value=t, props=typeProps[t], label=t)
+ clazz.AppendGroup (group)
+ return clazz
+
+def getPolylineClassification(polylines):
+ clazz = Classification()
+ waterProps = ClassGroupProperties()
+ waterProps.SetLineColor (Color (0.5, 0.5, 0.8))
+ minorProps = ClassGroupProperties()
+ minorProps.SetLineColor (Color (0.1, 0.9, 0.1))
+ majorProps = ClassGroupProperties()
+ majorProps.SetLineColor (Color (1, 1, 0.1))
+ roadProps = ClassGroupProperties()
+ roadProps.SetLineColor (Color (0.4, 0.4, 0.4))
+ railProps = ClassGroupProperties()
+ railProps.SetLineColor (Color (0.2, 0.2, 0.2))
+ earthProps = ClassGroupProperties()
+ earthProps.SetLineColor (Color (0.9, 0.6, 0.3))
+ boundProps = ClassGroupProperties()
+ boundProps.SetLineColor (Color (0.1, 0.1, 0.1))
+ dangerProps = ClassGroupProperties()
+ dangerProps.SetLineColor (Color (1, 0.1, 0.1))
+
+ typeProps = {'River channel': waterProps, 'Alley-thick': roadProps,
+ 'Principal Highway-thick': minorProps,
+ 'Intermittent River': waterProps, 'Airport Runway': roadProps,
+ 'Danger line': dangerProps, 'Ramp': roadProps,
+ 'Arterial Road-medium': minorProps,
+ 'Major Highway-thick': majorProps,
+ 'Principal Highway-medium': majorProps, 'River': waterProps,
+ 'International Boundary': boundProps,
+ 'County Boundary': boundProps, 'Political Boundary': boundProps,
+ 'Ferry': waterProps, 'Bridge': roadProps, 'Road': roadProps,
+ 'Stream-thin': waterProps, 'Track/Trail': earthProps,
+ 'Railroad': railProps, 'Unpaved Road-thin': earthProps,
+ 'Arterial Road-thick': minorProps, 'Road-thin': roadProps,
+ 'Major Highway Connector-thick': majorProps,
+ 'Roundabout': minorProps}
+
+ types = list(set([p['parsedType'] for p in polylines]))
+ for t in types:
+ if t in typeProps.keys():
+ group = ClassGroupSingleton (value=t, props=typeProps[t], label=t)
+ clazz.AppendGroup (group)
+ return clazz
+
+def getBackgroundClassification():
+ clazz = Classification()
+ bgProps = ClassGroupProperties()
+ bgProps.SetFill (Color (1, 1, 0.9))
+ grp = ClassGroupSingleton (value="0x4b", props=bgProps, label="Background")
+ clazz.AppendGroup (grp)
+ return clazz
+
+
+
+def import_mp_dialog(context):
+ """Request filename from user and run importing of mp file.
+
+ context -- The Thuban context.
+ """
+ dlg = wx.FileDialog(context.mainwindow,
+ _("Select MP file"), ".", "",
+ _("Polish Map Files (*.mp)|*.mp|") +
+ _("Text Files (*.txt)|*.txt|") +
+ _("All Files (*.*)|*.*"),
+ wx.OPEN|wx.OVERWRITE_PROMPT)
+ if dlg.ShowModal() == wx.ID_OK:
+ pfm_filename = internal_from_wxstring(dlg.GetPath())
+ dlg.Destroy()
+ else:
+ return
+
+ if pfm_filename.find ('.') == -1:
+ basename = filename
+ else:
+ basename = pfm_filename[:pfm_filename.rfind ('.')]
+
+ parser = pfm2shp (pfm_filename, basename)
+ # Now load the newly created shapefile
+ poi_filename = basename + '_poi.shp'
+ bg_filename = basename + '_bg.shp'
+ road_filename = basename + '_road.shp'
+ polygon_filename = basename + '_polygon.shp'
+ title = os.path.splitext(os.path.basename(pfm_filename))[0]
+ map = context.mainwindow.canvas.Map()
+ has_layers = map.HasLayers()
+
+ try:
+ store = context.session.OpenShapefile(bg_filename)
+ except IOError:
+ # the layer couldn't be opened
+ context.mainwindow.RunMessageBox(_('Add PFM Layer'),
+ _("Can't open the file '%s'.") % bg_filename)
+ else:
+ layer = Layer(title + ' - Background', store)
+ layer.SetClassificationColumn ('Type')
+ layer.SetClassification (getBackgroundClassification())
+ map.AddLayer(layer)
+ try:
+ store = context.session.OpenShapefile(poi_filename)
+ except IOError:
+ # the layer couldn't be opened
+ context.mainwindow.RunMessageBox(_('Add PFM Layer'),
+ _("Can't open the file '%s'.") % poi_filename)
+ else:
+ layer = Layer(title + ' - Points', store)
+ map.AddLayer(layer)
+
+ try:
+ store = context.session.OpenShapefile(polygon_filename)
+ except IOError:
+ # the layer couldn't be opened
+ context.mainwindow.RunMessageBox(_('Add PFM Layer'),
+ _("Can't open the file '%s'.") % polygon_filename)
+ else:
+ layer = Layer(title + ' - Polygons', store)
+ layer.SetClassificationColumn ('Type')
+ layer.SetClassification (getPolygonClassification(parser.polygons))
+ map.AddLayer(layer)
+
+ try:
+ store = context.session.OpenShapefile(road_filename)
+ except IOError:
+ # the layer couldn't be opened
+ context.mainwindow.RunMessageBox(_('Add PFM Layer'),
+ _("Can't open the file '%s'.") % road_filename)
+ else:
+ layer = Layer(title + ' - Lines', store)
+ layer.SetClassificationColumn ('Type')
+ layer.SetClassification (getPolylineClassification(parser.polylines))
+ map.AddLayer(layer)
+
+ if not has_layers:
+ # if we're adding a layer to an empty map, fit the
+ # new map to the window
+ context.mainwindow.canvas.FitMapToWindow()
+
+# register the new command
+registry.Add(Command('import-mp', _("(experimental) ")+_('Import PFM-file'), import_mp_dialog,
+ helptext = _('Import a Polish Map File')))
+
+# find the extension menu (create it anew if not found)
+extensions_menu = main_menu.FindOrInsertMenu('extensions',
+ _('E&xtensions'))
+
+# finally add the new entry to the menu
+extensions_menu.InsertItem('import-mp')
Added: trunk/thuban/Extensions/importMP/pfm.py
===================================================================
--- trunk/thuban/Extensions/importMP/pfm.py 2008-06-20 05:20:03 UTC (rev 2845)
+++ trunk/thuban/Extensions/importMP/pfm.py 2008-06-20 05:27:40 UTC (rev 2846)
@@ -0,0 +1,346 @@
+# Copyright (C) 2008 by Intevation GmbH
+# Authors:
+# Anthony Lenton <anthony at except.com.ar>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with Thuban for details.
+
+""" A Polish Map File parser """
+
+import re
+
+class MPParseError(Exception):
+ def __init__(self, lNum, reason):
+ self.reason = reason
+ self.lineNumber = lNum
+ def __str__(self):
+ return "Line %d: %s" % (self.lineNumber, repr(self.reason))
+ def __repr__(self):
+ return "Line %d: %s" % (self.lineNumber, repr(self.reason))
+
+polylineTypes = {'0x01': 'Major Highway-thick',
+ '0x02': 'Principal Highway-thick',
+ '0x03': 'Principal Highway-medium',
+ '0x04': 'Arterial Road-medium',
+ '0x05': 'Arterial Road-thick',
+ '0x06': 'Road-thin',
+ '0x07': 'Alley-thick',
+ '0x08': 'Ramp',
+ '0x09': 'Ramp',
+ '0x0a': 'Unpaved Road-thin',
+ '0x0b': 'Major Highway Connector-thick',
+ '0x0c': 'Roundabout',
+ '0x14': 'Railroad',
+ '0x15': 'Shoreline',
+ '0x16': 'Track/Trail',
+ '0x18': 'Stream-thin',
+ '0x19': 'Time-Zone',
+ '0x1a': 'Ferry',
+ '0x1b': 'Ferry',
+ '0x1c': 'Political Boundary',
+ '0x1d': 'County Boundary',
+ '0x1e': 'International Boundary',
+ '0x1f': 'River',
+ '0x20': 'Land Contour (thin)',
+ '0x21': 'Land Contour (medium)',
+ '0x22': 'Land Contour (thick)',
+ '0x23': 'Depth Contour (thin)',
+ '0x24': 'Depth Contour (medium)',
+ '0x25': 'Depth Contour (thick)',
+ '0x26': 'Intermittent River',
+ '0x27': 'Airport Runway',
+ '0x28': 'Pipeline',
+ '0x29': 'Power line',
+ '0x2a': 'Marine Boundary (no line)',
+ '0x2b': 'Marine Hazard (no line)',
+ '0x0100': 'Miscellaneous line',
+ '0x0101': 'Line',
+ '0x0102': 'Cartographic line',
+ '0x0103': 'Road',
+ '0x0104': 'Clearing line',
+ '0x0105': 'Contour line',
+ '0x0106': 'Overhead cable',
+ '0x0107': 'Bridge',
+ '0x0108': 'Recommended route',
+ '0x0109': 'Chart border',
+ '0x0300': 'Depth contour',
+ '0x0301': 'Depth contour value',
+ '0x0307': 'Intertidal zone border',
+ '0x0400': 'Obstruction line',
+ '0x0401': 'Submarine cable',
+ '0x0402': 'Submarine pipeline',
+ '0x0403': 'Pile barrier',
+ '0x0404': 'Fishing stakes',
+ '0x0405': 'Supply pipeline area',
+ '0x0406': 'Submarine cable area',
+ '0x0407': 'Dumping ground',
+ '0x0408': 'Explosive dumping ground',
+ '0x0409': 'Danger line',
+ '0x040a': 'Overhead cable',
+ '0x040b': 'Submerged construction',
+ '0x040c': 'Pier/jetty',
+ '0x0500': 'Restriction',
+ '0x0501': 'Anchoring prohibited',
+ '0x0502': 'Fishing prohibited',
+ '0x0503': 'Prohibited area',
+ '0x0504': 'Military practice area',
+ '0x0505': 'Anchoring and fishing prohibited',
+ '0x0506': 'Limit of nature reservation',
+ '0x0507': 'Restricted area',
+ '0x0508': 'Minefield',
+ '0x0600': 'Miscellaneous line',
+ '0x0601': 'Cartographic line',
+ '0x0602': 'Traffic separation line',
+ '0x0603': 'International maritime boundary',
+ '0x0604': 'Straight territorial sea baseline',
+ '0x0605': 'Seaward limit of territorial sea',
+ '0x0606': 'Anchorage area',
+ '0x0607': 'Quarantine anchorage area',
+ '0x0608': 'Fishery zone',
+ '0x0609': 'Swept area',
+ '0x060a': 'Traffic separation zone',
+ '0x060b': 'Limit of exclusive economic zone',
+ '0x060c': 'Established direction of traffic flow',
+ '0x060d': 'Recommended direction of traffic flow',
+ '0x060e': 'Harbour limit',
+ '0x060f': 'Inadequately surveyed area',
+ '0x0610': 'Inshore traffic zone',
+ '0x0611': 'Limit of traffic lane',
+ '0x0701': 'River channel',
+ '0x0702': 'Submerged object',
+ '0x0706': 'Chart boundary'}
+
+polygonTypes = {'0x01': 'City',
+ '0x02': 'City',
+ '0x03': 'City',
+ '0x04': 'Military',
+ '0x05': 'Car Park (Parking Lot)',
+ '0x06': 'Parking Garage',
+ '0x07': 'Airport',
+ '0x08': 'Shopping Centre',
+ '0x09': 'Marina',
+ '0x0a': 'University',
+ '0x0b': 'Hospital',
+ '0x0c': 'Industrial',
+ '0x0d': 'Reservation',
+ '0x0e': 'Airport Runway',
+ '0x13': 'Man made area',
+ '0x14': 'National park',
+ '0x15': 'National park',
+ '0x16': 'National park',
+ '0x17': 'City Park',
+ '0x18': 'Golf',
+ '0x19': 'Sport',
+ '0x1a': 'Cemetery',
+ '0x1e': 'State Park',
+ '0x1f': 'State Park',
+ '0x28': 'Ocean',
+ '0x3b': 'Blue-Unknown',
+ '0x32': 'Sea',
+ '0x3b': 'Blue-Unknown',
+ '0x3c': 'Lake',
+ '0x3d': 'Lake',
+ '0x3e': 'Lake',
+ '0x3f': 'Lake',
+ '0x40': 'Lake',
+ '0x41': 'Lake',
+ '0x42': 'Lake',
+ '0x43': 'Lake',
+ '0x44': 'Lake',
+ '0x45': 'Blue-Unknown',
+ '0x46': 'River',
+ '0x47': 'River',
+ '0x48': 'River',
+ '0x49': 'River',
+ '0x4b': 'Background',
+ '0x4c': 'Intermittent River/Lake',
+ '0x4d': 'Glacier',
+ '0x4e': 'Orchard or plantation',
+ '0x4f': 'Scrub',
+ '0x50': 'Woods',
+ '0x51': 'Wetland',
+ '0x52': 'Tundra',
+ '0x53': 'Flats',
+ '0x0100': 'Land - white',
+ '0x0101': 'Land - non-urban',
+ '0x0102': 'Land - urban',
+ '0x0103': 'Chart exclusion area',
+ '0x0104': 'Chart background',
+ '0x0105': 'Bridge',
+ '0x0300': 'Depth area - white 1',
+ '0x0301': 'Intertidal zone',
+ '0x0302': 'Depth area - blue 1',
+ '0x0303': 'Depth area - blue 2',
+ '0x0304': 'Depth area - blue 3',
+ '0x0305': 'Depth area - blue 4',
+ '0x0306': 'Depth area - blue 5',
+ '0x0307': 'Depth area - white',
+ '0x0400': 'Obstruction (invisible)',
+ '0x0401': 'Submarine cable (invisible)',
+ '0x0402': 'Submarine pipeline (invisible)',
+ '0x0403': 'Pile barrier (invisible)',
+ '0x0404': 'Fishing stakes (invisible)',
+ '0x0405': 'Supply pipeline area/line (invisible)',
+ '0x0406': 'Submarine cable area/line (invisible)',
+ '0x0407': 'Dumping ground (invisible)',
+ '0x0408': 'Explosive dumping ground (invisible)',
+ '0x0409': 'Danger line (invisible)',
+ '0x040a': 'Overhead cable (invisible)',
+ '0x040b': 'Submerged construction (invisible)',
+ '0x040c': 'Pier/jetty (invisible)',
+ '0x0500': 'Restriction area/line (invisible)',
+ '0x0501': 'Anchoring prohibited (invisible)',
+ '0x0502': 'Fishing prohibited (invisible)',
+ '0x0503': 'Prohibited area (invisible)',
+ '0x0504': 'Military practice area (invisible)',
+ '0x0505': 'Anchoring and fishing prohibited (invisible)',
+ '0x0506': 'Limit of nature reservation (invisible)',
+ '0x0507': 'Restricted area (invisible)',
+ '0x0508': 'Minefield (invisible)',
+ '0x0600': 'Miscellaneous area',
+ '0x0601': 'Cartographic area',
+ '0x0602': 'Traffic separation area',
+ '0x0603': 'International maritime boundary',
+ '0x0604': 'Straight territorial sea baseline',
+ '0x0605': 'Seaward limit of territorial sea',
+ '0x0606': 'Anchorage area',
+ '0x0607': 'Quarantine anchorage area',
+ '0x0608': 'Fishery zone',
+ '0x0609': 'Swept area',
+ '0x060a': 'Traffic separation zone',
+ '0x060b': 'Limit of exclusive economic zone',
+ '0x060c': 'Established direction of traffic flow',
+ '0x0701': 'Fishing area',
+ '0x0702': 'Restricted area',
+ '0x0703': 'Anchorage area',
+ '0x0704': 'Fishing Hot Spots chart'}
+
+class MPParser(object):
+ fields = {'poi': ['Data0', 'Label', 'Origin0'],
+ 'rgn10': ['Data0', 'Label', 'Origin0'],
+ 'rgn20': ['Data0', 'Label', 'Origin0'],
+ 'polyline': ['Type', 'Data0', 'Origin0', 'Label'],
+ 'rgn40': ['Type', 'Data0', 'Origin0', 'Label'],
+ 'polygon': ['Type', 'Data0', 'Origin0', 'Label'],
+ 'rgn80': ['Type', 'Data0', 'Origin0', 'Label'],
+ 'img id': ['Name'] }
+ def __init__(self):
+ self.pois = []
+ self.polygons = []
+ self.polylines = []
+ self.parsedLines = 0
+ self.parsedSections = 0
+ self.mapName = ""
+
+ def parse (self, fp):
+ def sectionBounder (line):
+ return line[0] == '[' and line[-1] == ']'
+ def sectionEnd (line):
+ return (sectionBounder (line) and
+ (line.lower() == '[end]' or
+ line.lower().startswith('[end-')))
+ def sectionStart (line):
+ return sectionBounder (line) and not sectionEnd (line)
+ parsed = {}
+ for kind in self.fields.keys():
+ parsed[kind] = []
+ inSection = False
+ sectionName = None
+ lNum = 0 # Number of lines parsed so far
+ sNum = 0 # Number of sections parsed so far
+ for line in fp.readlines():
+ lNum += 1
+ line = line.strip()
+ if len(line) == 0 or line[0] == ';': # Drop blank lines and comments
+ continue
+ if not inSection:
+ if not sectionBounder (line):
+ raise MPParseError (lNum, "Line not within a section")
+ elif sectionEnd (line):
+ raise MPParseError (lNum, "Section End without a section")
+ else:
+ sectionName = line[1:-1].lower()
+ section = {}
+ inSection = True
+ sNum += 1
+ else:
+ if not sectionBounder (line):
+ if sectionName in self.fields.keys():
+ parts = line.split('=',1)
+ if len(parts) != 2:
+ raise MPParseError (lNum, "Invalid line")
+ key, value = map(str.strip, parts)
+ if key in self.fields[sectionName]:
+ section[key] = value
+ elif sectionStart (line):
+ raise MPParseError (lNum, "Section Start within a section")
+ else:
+ if line.startswith ('[end-') and line[5:-1] != sectionName:
+ raise MPParseError (lNum, "Section name mismatch")
+ if sectionName in self.fields.keys():
+ parsed[sectionName].append (section)
+ inSection = False
+ # These last checks are a bit pedantic (not needed to do our
+ # job) but we're validating the source file a bit.
+ if inSection:
+ raise MPParseError (lNum, "End-of-file found within a section")
+ if len(parsed['img id']) == 0:
+ raise MPParseError (lNum, "No [IMG ID] section found")
+ elif len(parsed['img id']) > 1:
+ raise MPParseError (lNum, "Multiple [IMG ID] sections found")
+ else:
+ self.mapName = parsed['img id'][0].get ('Name', "")
+
+ # Ok, enough checks, set the data!
+ self.parsedLines = lNum
+ self.parsedSections = sNum
+
+ self.pois = parsed['poi'] + parsed['rgn10'] + parsed['rgn20']
+ twople = re.compile(r'\([-+]?(?P<lat>\d+(\.\d*)?|\.\d+), ' \
+ r'*[-+]?(?P<long>\d+(\.\d*)?|\.\d+)\)')
+ for poi in self.pois:
+ assert poi.has_key ('Data0') or poi.has_key ('Origin0')
+ data = poi.get('Data0', poi.get('Origin0'))
+ match = twople.match (data)
+ if not match is None:
+ poi['long'] = -float (match.group('long'))
+ poi['lat'] = -float (match.group('lat'))
+ self.polygons = parsed['polygon'] + parsed['rgn80']
+ for poly in self.polygons:
+ poly['long'] = []
+ poly['lat'] = []
+ assert poly.has_key ('Data0') or poly.has_key ('Origin0')
+ data = poly.get('Data0', poly.get('Origin0'))
+ while len(data) > 0:
+ match = twople.match (data)
+ if not match is None:
+ poly['long'].append (-float (match.group('long')))
+ poly['lat'].append (-float (match.group('lat')))
+ data = data[match.end() + 1:]
+ try:
+ poly['parsedType'] = polygonTypes[poly['Type'], ""]
+ except KeyError, e:
+ # Attempt to read 0xe as 0x0e, for example
+ newType = '0x0' + poly['Type'][2:]
+ poly['parsedType'] = polygonTypes.get(newType, "")
+
+ self.polylines = parsed['polyline'] + parsed['rgn40']
+ for poly in self.polylines:
+ poly['long'] = []
+ poly['lat'] = []
+ assert poly.has_key ('Data0') or poly.has_key ('Origin0')
+ data = poly.get('Data0', poly.get('Origin0'))
+ while len(data) > 0:
+ match = twople.match (data)
+ if not match is None:
+ poly['long'].append (-float (match.group('long')))
+ poly['lat'].append (-float (match.group('lat')))
+ data = data[match.end() + 1:]
+ try:
+ poly['parsedType'] = polylineTypes[poly['Type']]
+ except KeyError, e:
+ # Attempt to read 0xe as 0x0e, for example
+ newType = '0x0' + poly['Type'][2:]
+ poly['parsedType'] = polygonTypes.get(newType, "")
+ return parsed
+
Added: trunk/thuban/Extensions/importMP/test/snippets.py
===================================================================
--- trunk/thuban/Extensions/importMP/test/snippets.py 2008-06-20 05:20:03 UTC (rev 2845)
+++ trunk/thuban/Extensions/importMP/test/snippets.py 2008-06-20 05:27:40 UTC (rev 2846)
@@ -0,0 +1,456 @@
+# This file contains snippets used by the testing functions.
+# They should be in alphabetical order
+
+bounderMismatch = """
+[img 1]
+[end-yonk]
+"""
+
+caseSensitivity1 = """
+[IMG ID]
+[END-IMG ID]
+"""
+
+caseSensitivity2 = """
+[img ID]
+[enD-IMG id]
+"""
+
+caseSensitivity3 = """
+[ImG Id]
+[EnD-imG iD]
+"""
+
+doubleSectionClose = """
+[img id]
+[end-img id]
+[end-img id]
+"""
+
+headerWithComments = """[img id]
+This=is a test MPFile
+;I have no idea what it's about, But I shouldn't care
+; I do know that this is a comment
+; So it should be removed.
+; And blank lines should be removed too.
+
+=This isn't a comment.
+T=his; isn't either
+ ; let's say this is a comment
+ ; to be a bit more robust
+[end]
+"""
+
+incompleteSection = """
+[img id]
+"""
+
+justHeader = """
+[img id]
+Dummy=I'm a fake MPFile
+[end]
+"""
+
+mapWithName = """
+[img id]
+Name = My Test Map
+[end]
+"""
+
+miniLobos = """
+[IMG ID]
+CodePage=850
+LblCoding=9
+ID=54010076
+Name=Lobos
+[END-IMG ID]
+
+[Countries]
+Country1=Argentina~[0x1d]ARG
+[END-Countries]
+
+[Regions]
+Region1=Buenos Aires~[0x1d]BA
+CountryIdx1=1
+[END-Regions]
+
+[Cities]
+City1=Abbott
+RegionIdx1=1
+[END-Cities]
+
+[ZipCodes]
+ZipCode1=7200
+[END-ZipCodes]
+
+[Restrict]
+Nod=155306
+TraffPoints=155988,155306,155988
+TraffRoads=8609,8609
+Time=
+[END-Restrict]
+
+[DICTIONARY]
+; 0 1 2 3 4
+; 1234567890123456789012345678901234567890123
+Level1RGN40=1111111111110000000111011111111111111111111
+Level2RGN40=1111111111110000000111011111111111111111111
+Level3RGN40=1111100101110000000111011111111011011101100
+Level4RGN40=1111000001110000000010001111111001001000000
+Level5RGN40=1100000000000000000010001011110000000000000
+Level6RGN40=1000000000000000000010001000010000000000000
+[END-DICTIONARY]
+
+[POI]
+Type=0x2500
+Label=Peaje Saladillo
+EndLevel=6
+StreetDesc=Ruta 205 Km. 167,00
+Data0=(-35.51966,-59.67464)
+[END]
+
+[POI]
+Type=0x4400
+Label=ACA
+EndLevel=6
+Data0=(-35.70035,-58.88981)
+[END]
+
+[POI]
+Type=0x5903
+Label=Saladillo
+EndLevel=6
+Data0=(-35.60043,-59.81360)
+[END]
+
+[POI]
+Type=0x6401
+EndLevel=6
+Data0=(-35.65026,-58.85069)
+[END]
+
+[POLYLINE]
+Type=0xa
+EndLevel=6
+RoadID=1970
+RouteParam=3,0,0,0,0,0,0,0,0,0,0,0
+Data0=(-35.07797,-59.09923),(-35.08840,-59.11088)
+Nod1=0,34258,0
+Nod2=1,34259,0
+[END]
+
+[POLYLINE]
+Type=0xa
+EndLevel=6
+RoadID=1973
+RouteParam=3,0,0,0,0,0,0,0,0,0,0,0
+Data0=(-35.24004,-59.48067),(-35.24277,-59.49114),(-35.24497,-59.49020)
+Nod1=0,29389,0
+Nod2=2,29386,0
+[END]
+
+[POLYGON]
+Type=0x17
+Label=Plaza Arturo Illia
+EndLevel=6
+Data0=(-35.76743,-58.49350),(-35.76776,-58.49378),(-35.76782,-58.49371)
+[END]
+
+"""
+
+mixedPois = """
+[IMG ID]
+[END]
+
+[RGN20]
+Type=0xd
+Label=BIALET MASSE
+City=Y
+CityIdx=17
+Origin0=(-31.315284,-64.461891)
+[END-RGN20]
+
+[RGN20]
+Type=0xc
+Label=VALLE HERMOSO
+City=Y
+CityIdx=184
+Origin0=(-31.117530,-64.482666)
+[END-RGN20]
+
+[RGN10]
+Type=0x3002
+Label=MATERNIDAD
+CityIdx=44
+Origin0=(-31.423515,-64.163887)
+[END-RGN10]
+
+[RGN10]
+Type=0x2b01
+Label=HOTEL
+CityIdx=92
+Origin0=(-31.092639,-64.477066)
+[END-RGN10]
+
+[POI]
+Type=0x2f08
+Label=Est. Salvador Maria
+EndLevel=6
+StreetDesc=F. C. G. R.
+CityIdx=28
+Data0=(-35.30298,-59.16738)
+[END]
+
+[POI]
+Type=0xe00
+Label=Salvador Maria
+EndLevel=6
+City=Y
+CityIdx=28
+Data0=(-35.29979,-59.16765)
+[END]
+
+"""
+
+mixedRGN40sAndPolylines = """
+[IMG ID]
+[END]
+
+[RGN40]
+Type=0x18
+Data0=(-31.172805,-64.999877),(-31.170059,-64.997474)
+[END-RGN40]
+
+[RGN40]
+Type=0x18
+Data0=(-31.124225,-64.999877),(-31.137958,-64.982543)
+[END-RGN40]
+
+[POLYLINE]
+Type=0xa
+EndLevel=6
+RoadID=4532
+RouteParam=3,0,0,0,0,0,0,0,0,0,0,0
+Data0=(-35.39861,-59.34420),(-35.40269,-59.34945)
+Nod1=0,29465,0
+Nod2=1,29456,0
+[END]
+
+[POLYLINE]
+Type=0xa
+EndLevel=6
+RoadID=4433
+RouteParam=3,0,0,0,0,0,0,0,0,0,0,0
+Data0=(-35.38428,-59.32179),(-35.37814,-59.31504)
+Nod1=0,29646,0
+Nod2=1,34580,0
+[END]
+
+"""
+
+mixedRGN80sAndPolygons = """
+[IMG ID]
+[END]
+
+[RGN80]
+Type=0x41
+Label=LA QUINTANA
+Data0=(-31.839323,-64.431465),(-31.843271,-64.430091),(-31.848936,-64.435241)
+[END-RGN80]
+
+[RGN80]
+Type=0x3c
+Label=EMBALSE LOS MOLINOS
+Data0=(-31.826664,-64.571586),(-31.826664,-64.572616),(-31.826320,-64.572273)
+[END-RGN80]
+
+[POLYGON]
+Type=0x19
+EndLevel=6
+Data0=(-35.40248,-59.32782),(-35.40348,-59.32778),(-35.40357,-59.32937)
+[END]
+
+[POLYGON]
+Type=0xb
+EndLevel=6
+Data0=(-35.40273,-59.32503),(-35.40338,-59.32611),(-35.40402,-59.32553)
+[END]
+
+"""
+
+nonsenseBounder = """
+[img id]
+[yonk]
+"""
+
+nothingness = ""
+
+RGN10s = """
+[IMG ID]
+[END]
+
+[RGN10]
+Type=0x2f04
+Label=FALDA DEL CARMEN
+CityIdx=6
+Origin0=(-31.583763,-64.456230)
+[END-RGN10]
+
+[RGN10]
+Type=0x2f03
+Label=BERTA MOTORSPORT
+CityIdx=6
+Origin0=(-31.647449,-64.408508)
+[END-RGN10]
+
+[RGN10]
+Type=0x2f01
+Label=ESSO
+CityIdx=6
+Origin0=(-31.658950,-64.409706)
+[END-RGN10]
+
+[RGN10]
+Type=0x2f01
+Label=YPF
+CityIdx=6
+Origin0=(-31.658950,-64.409019)
+[END-RGN10]
+"""
+
+RGN20s = """
+[IMG ID]
+[END]
+
+[RGN20]
+Type=0xc
+Label=MINA CLAVERO
+City=Y
+CityIdx=122
+Origin0=(-31.724071,-65.003494)
+[END-RGN20]
+
+[RGN20]
+Type=0x6
+Label=VILLA GENERAL BELGRANO
+City=Y
+CityIdx=200
+Origin0=(-31.976524,-64.558883)
+[END-RGN20]
+
+[RGN20]
+Type=0xb
+Label=LA FALDA
+City=Y
+CityIdx=92
+Origin0=(-31.099506,-64.477340)
+[END-RGN20]
+
+[RGN20]
+Type=0xd
+Label=VILLA CAEIRO
+City=Y
+CityIdx=190
+Origin0=(-31.291595,-64.462577)
+[END-RGN20]
+"""
+
+RGN40s = """
+[IMG ID]
+[END]
+
+[RGN40]
+Type=0x1f
+Label=RIO LOS CONDORITOS
+Data0=(-31.688133,-64.676124),(-31.686073,-64.674064),(-31.685730,-64.673210)
+[END-RGN40]
+
+[RGN40]
+Type=0x18
+Data0=(-31.463600,-64.859291),(-31.451927,-64.880577),(-31.446777,-64.897911)
+[END-RGN40]
+
+[RGN40]
+Type=0x18
+Data0=(-31.529861,-64.898086),(-31.530376,-64.900314),(-31.526428,-64.909240)
+[END-RGN40]
+
+[RGN40]
+Type=0x14
+Data0=(-30.821243,-64.993354),(-30.830513,-64.999877)
+[END-RGN40]
+"""
+
+RGN80s = """
+[IMG ID]
+[END]
+
+[RGN80]
+Type=0x41
+Data0=(-30.129791,-64.816200),(-30.133224,-64.814140),(-30.137859,-64.818092)
+[END-RGN80]
+
+[RGN80]
+Type=0x3
+Data0=(-30.712581,-64.813629),(-30.713096,-64.808303),(-30.720478,-64.803672)
+[END-RGN80]
+
+[RGN80]
+Type=0x41
+Label=LA QUINTANA
+Data0=(-31.839323,-64.431465),(-31.843271,-64.430091),(-31.848936,-64.435241)
+[END-RGN80]
+
+[RGN80]
+Type=0x3c
+Label=EMBALSE LOS MOLINOS
+Data0=(-31.826664,-64.571586),(-31.826664,-64.572616),(-31.826320,-64.572273)
+[END-RGN80]
+
+"""
+
+
+twoHeaderSections = """
+[img id]
+[end]
+; And now, for something completely different:
+[img id]
+[end]
+"""
+
+weirdTypes = """
+[IMG ID]
+[END]
+
+[POI]
+Type=0x999999
+Data0=(-35.51966,-59.67464)
+[END]
+
+[POI]
+Type=-3.14e27
+Data0=(-35.60043,-59.81360)
+[END]
+
+[POI]
+Type=Sarasa
+Data0=(-35.70035,-58.88981)
+[END]
+
+[POLYLINE]
+Type=0x123456789
+Data0=(-35.07797,-59.09923),(-35.08840,-59.11088)
+[END]
+
+[POLYLINE]
+Type=1919191919191919191919191919
+Data0=(-35.24004,-59.48067),(-35.24277,-59.49114),(-35.24497,-59.49020)
+[END]
+
+[POLYGON]
+Type=NobodyExpectsTheSpanishInquisition
+Data0=(-35.76743,-58.49350),(-35.76776,-58.49378),(-35.76782,-58.49371)
+[END]
+
+"""
+
Added: trunk/thuban/Extensions/importMP/test/test_pfm.py
===================================================================
--- trunk/thuban/Extensions/importMP/test/test_pfm.py 2008-06-20 05:20:03 UTC (rev 2845)
+++ trunk/thuban/Extensions/importMP/test/test_pfm.py 2008-06-20 05:27:40 UTC (rev 2846)
@@ -0,0 +1,123 @@
+# Copyright (c) 2008 by Intevation GmbH
+# Authors:
+# Anthony Lenton <anthony at except.com.ar>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with Thuban for details.
+
+"""
+Tests for the PFM importer.
+"""
+import unittest
+from StringIO import StringIO
+from Extensions.importMP.pfm import MPParser, MPParseError
+import snippets
+
+class TestPFM (unittest.TestCase):
+ def testInitialValue(self):
+ """ Check that the parser has good initial values """
+ parser = MPParser()
+ self.assertEquals (parser.mapName, "")
+ self.assertEquals (parser.parsedLines, 0)
+ self.assertEquals (parser.parsedSections, 0)
+ self.assertEquals (parser.pois, [])
+ self.assertEquals (parser.polygons, [])
+ self.assertEquals (parser.polylines, [])
+
+ def testParsedLines(self):
+ """ Check that comments and blank lines are skipped correctly """
+ parser = MPParser()
+ parser.parse (StringIO(snippets.justHeader))
+ self.assertEquals (parser.parsedLines, 4)
+ parser = MPParser()
+ parser.parse (StringIO(snippets.headerWithComments))
+ self.assertEquals (parser.parsedLines, 12)
+
+ def testSections (self):
+ """ Check that sections are matched up properly """
+ parser = MPParser()
+ parser.parse (StringIO(snippets.justHeader))
+ self.assertEquals (parser.parsedSections, 1)
+ parser = MPParser()
+ mp = StringIO (snippets.nonsenseBounder)
+ self.assertRaises (MPParseError, parser.parse, mp)
+ parser = MPParser()
+ mp = StringIO (snippets.bounderMismatch)
+ self.assertRaises (MPParseError, parser.parse, mp)
+ parser = MPParser()
+ mp = StringIO (snippets.doubleSectionClose)
+ self.assertRaises (MPParseError, parser.parse, mp)
+
+ def testCaseInsensitive (self):
+ """ Check that no exception is raised if capitals are used"""
+ parser = MPParser()
+ parser.parse (StringIO (snippets.caseSensitivity1))
+ parser = MPParser()
+ parser.parse (StringIO (snippets.caseSensitivity2))
+ parser = MPParser()
+ parser.parse (StringIO (snippets.caseSensitivity3))
+
+ def testIncompleteSection (self):
+ """ Check that an exception is raised if the last section is
+ left open """
+ parser = MPParser()
+ mp = StringIO(snippets.incompleteSection)
+ self.assertRaises (MPParseError, parser.parse, mp)
+
+ def testHeaderSection (self):
+ """ Check that the header section is parsed """
+ parser = MPParser ()
+ mp = StringIO(snippets.nothingness)
+ self.assertRaises (MPParseError, parser.parse, mp)
+ parser = MPParser()
+ mp = StringIO(snippets.twoHeaderSections)
+ self.assertRaises (MPParseError, parser.parse, mp)
+ parser = MPParser()
+ parser.parse(StringIO(snippets.mapWithName))
+ self.assertEquals (parser.mapName, "My Test Map")
+
+ def testSample (self):
+ parser = MPParser()
+ parser.parse (StringIO(snippets.miniLobos))
+ self.assertEquals(len(parser.pois), 4)
+ self.assertEquals(len(parser.polylines), 2)
+ self.assertEquals(len(parser.polygons), 1)
+
+ def testPolylines (self):
+ parser = MPParser()
+ parser.parse (StringIO(snippets.RGN40s))
+ self.assertEquals(len(parser.polylines), 4)
+ parser = MPParser()
+ parser.parse (StringIO(snippets.mixedRGN40sAndPolylines))
+ self.assertEquals(len(parser.polylines), 4)
+
+ def testPolygons (self):
+ parser = MPParser()
+ parser.parse (StringIO(snippets.RGN80s))
+ self.assertEquals(len(parser.polygons), 4)
+ parser = MPParser()
+ parser.parse (StringIO(snippets.mixedRGN80sAndPolygons))
+ self.assertEquals(len(parser.polygons), 4)
+
+ def testPois (self):
+ parser = MPParser()
+ parser.parse (StringIO(snippets.RGN10s))
+ self.assertEquals(len(parser.pois), 4)
+ parser = MPParser()
+ parser.parse (StringIO(snippets.RGN20s))
+ self.assertEquals(len(parser.pois), 4)
+ parser = MPParser()
+ parser.parse (StringIO(snippets.mixedPois))
+ self.assertEquals(len(parser.pois), 6)
+
+ def testDontCrashWithWeirdTypes (self):
+ parser = MPParser()
+ parser.parse (StringIO(snippets.weirdTypes))
+ self.assertEquals(len(parser.pois), 3)
+ self.assertEquals(len(parser.polylines), 2)
+ self.assertEquals(len(parser.polygons), 1)
+
+
+if __name__ == "__main__":
+ unittest.main()
+
More information about the Thuban-commits
mailing list