[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