[Thuban-commits] r2861 - in trunk/thuban: Extensions Extensions/gMapTiles Extensions/gMapTiles/sample Extensions/gMapTiles/test Thuban
scm-commit@wald.intevation.org
scm-commit at wald.intevation.org
Wed Jul 30 07:52:31 CEST 2008
Author: elachuni
Date: 2008-07-30 07:52:31 +0200 (Wed, 30 Jul 2008)
New Revision: 2861
Added:
trunk/thuban/Extensions/gMapTiles/
trunk/thuban/Extensions/gMapTiles/README.txt
trunk/thuban/Extensions/gMapTiles/__init__.py
trunk/thuban/Extensions/gMapTiles/exportGMapTiles.py
trunk/thuban/Extensions/gMapTiles/exporter.py
trunk/thuban/Extensions/gMapTiles/sample/
trunk/thuban/Extensions/gMapTiles/sample/README.txt
trunk/thuban/Extensions/gMapTiles/sample/sample.html
trunk/thuban/Extensions/gMapTiles/sample/tile.py
trunk/thuban/Extensions/gMapTiles/test/
trunk/thuban/Extensions/gMapTiles/test/test_gMapTileExport.py
Modified:
trunk/thuban/Thuban/thuban_cfg.py
Log:
First version of the Google Maps Tiles (exporter) extension.
Added: trunk/thuban/Extensions/gMapTiles/README.txt
===================================================================
--- trunk/thuban/Extensions/gMapTiles/README.txt 2008-07-30 03:18:02 UTC (rev 2860)
+++ trunk/thuban/Extensions/gMapTiles/README.txt 2008-07-30 05:52:31 UTC (rev 2861)
@@ -0,0 +1,10 @@
+This extension generates 256x256px png tiles out of Thuban maps, to be able to
+overlay the data on Google Maps.
+
+The extension reprojects and chops the tiles accordingly so that the data
+matches Google's imagery.
+
+For now the extension only generates the tiles, you'll need to write your
+own javascript to use them. Check the "sample/" subdirectory for a very simple
+way of viewing the data you generate.
+
Added: trunk/thuban/Extensions/gMapTiles/__init__.py
===================================================================
--- trunk/thuban/Extensions/gMapTiles/__init__.py 2008-07-30 03:18:02 UTC (rev 2860)
+++ trunk/thuban/Extensions/gMapTiles/__init__.py 2008-07-30 05:52:31 UTC (rev 2861)
@@ -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 exportGMapTiles
+
+# perform the registration of the extension
+from Thuban import _
+from Thuban.UI.extensionregistry import ExtensionDesc, ext_registry
+
+ext_registry.add(ExtensionDesc(
+ name = 'exportGMapTiles',
+ version = '0.1.1',
+ authors= [ 'Anthony Lenton' ],
+ copyright = '2008 by Intevation GmbH',
+ desc = _("Export PNG tiles to be loaded with Google Maps\n" \
+ "as a GTileLayerOverlay.")))
+
Added: trunk/thuban/Extensions/gMapTiles/exportGMapTiles.py
===================================================================
--- trunk/thuban/Extensions/gMapTiles/exportGMapTiles.py 2008-07-30 03:18:02 UTC (rev 2860)
+++ trunk/thuban/Extensions/gMapTiles/exportGMapTiles.py 2008-07-30 05:52:31 UTC (rev 2861)
@@ -0,0 +1,134 @@
+# 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.
+
+""" This module allows you to save your data as png files to be served as a
+ Google Maps layer overlay. """
+
+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 exporter import export
+
+DEFAULT_DESTINATION_FOLDER = os.path.join (os.getcwd(), "tiles")
+
+class exportGMapTilesDialog(wx.Dialog):
+ def __init__(self, parent, ID, title,
+ pos=wx.DefaultPosition, size=wx.DefaultSize,
+ style=wx.DEFAULT_DIALOG_STYLE):
+
+ # initialize the Dialog
+ wx.Dialog.__init__(self, parent, ID, title, pos, size, style)
+
+ # Destination folder
+ box_folder = wx.BoxSizer(wx.HORIZONTAL)
+ box_folder.Add(wx.StaticText(self, -1, _("Destination folder:")), 1,
+ wx.ALL|wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL, 4)
+ self.text_folder = wx.TextCtrl(self, -1, DEFAULT_DESTINATION_FOLDER)
+ box_folder.Add(self.text_folder, 2, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 4)
+ self.button_folder = wx.Button (self, -1, _("Choose folder"))
+ self.button_folder.Bind (wx.EVT_BUTTON, self.OnChooseFolder)
+ box_folder.Add(self.button_folder, 1,wx.ALL|wx.ALIGN_CENTER_VERTICAL, 4)
+
+ # Zoom levels
+ box_zoom = wx.BoxSizer (wx.HORIZONTAL)
+ box_zoom.Add (wx.StaticText (self, -1, _("Minimum Zoom:")), 0,
+ wx.ALL|wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL, 4)
+ self.minZoomSpin = wx.SpinCtrl (self, size=(60, -1), min=1, max=17)
+ self.Bind(wx.EVT_SPINCTRL, self.OnMinZoomChanged, self.minZoomSpin)
+ box_zoom.Add (self.minZoomSpin, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 4)
+ box_zoom.Add (wx.StaticText (self, -1, _("Maximum Zoom:")), 0,
+ wx.ALL|wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL, 4)
+ self.maxZoomSpin = wx.SpinCtrl (self, size=(60, -1), min=1, max=17)
+ self.Bind(wx.EVT_SPINCTRL, self.OnMaxZoomChanged, self.maxZoomSpin)
+ box_zoom.Add (self.maxZoomSpin, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 4)
+
+ #buttons
+ box_buttons = wx.BoxSizer(wx.HORIZONTAL)
+ button = wx.Button(self, wx.ID_OK, _("OK"))
+ box_buttons.Add(button, 0, wx.ALL, 5)
+ button = wx.Button(self, wx.ID_CANCEL, _("Cancel"))
+ box_buttons.Add(button, 0, wx.ALL, 5)
+ #set the button funcitons
+ self.Bind(wx.EVT_BUTTON, self.OnOK, id=wx.ID_OK)
+ self.Bind(wx.EVT_BUTTON, self.OnCancel, id=wx.ID_CANCEL)
+
+ # compose the final dialog
+ top = wx.BoxSizer(wx.VERTICAL)
+ top.Add(box_folder, 0, wx.EXPAND |wx.ALL, 5)
+ top.Add(box_zoom, 0, wx.EXPAND |wx.ALL, 5)
+ top.Add(box_buttons, 0, wx.ALIGN_RIGHT)
+
+ # final layout settings
+ self.SetSizer(top)
+ top.Fit(self)
+
+ def OnMinZoomChanged (self, event):
+ if self.maxZoomSpin.GetValue() < self.minZoomSpin.GetValue():
+ self.maxZoomSpin.SetValue(self.minZoomSpin.GetValue())
+
+ def OnMaxZoomChanged(self, event):
+ if self.minZoomSpin.GetValue() > self.maxZoomSpin.GetValue():
+ self.minZoomSpin.SetValue(self.maxZoomSpin.GetValue())
+
+ def OnChooseFolder (self, event):
+ dlg = wx.DirDialog(self, _("Select a folder to save the tiles in"), ".")
+ if dlg.ShowModal() == wx.ID_OK:
+ pfm_filename = internal_from_wxstring(dlg.GetPath())
+ self.text_folder.ChangeValue (pfm_filename)
+ dlg.Destroy()
+
+ def RunDialog(self):
+ self.ShowModal()
+ self.Destroy()
+
+ def endDialog(self, result):
+ self.result = result
+ if self.result is not None:
+ self.EndModal(wx.ID_OK)
+ else:
+ self.EndModal(wx.ID_CANCEL)
+ self.Show(False)
+
+ def OnOK(self, event):
+ self.result = "OK"
+ self.dataFolder = self.text_folder.GetValue()
+ self.minZoom = self.minZoomSpin.GetValue()
+ self.maxZoom = self.maxZoomSpin.GetValue()
+ self.endDialog(self.result)
+
+ def OnCancel(self, event):
+ self.endDialog(None)
+
+
+def export_gmap_dialog(context):
+ """ Select the destination folder and zoom levels needed.
+
+ context -- The Thuban context.
+ """
+ dlg = exportGMapTilesDialog(context.mainwindow, -1,
+ _("Export Google Maps tiles"))
+ dlg.RunDialog()
+ if dlg.result == "OK":
+ export (context, dlg.minZoom, dlg.maxZoom, dlg.dataFolder)
+
+# register the new command
+registry.Add(Command('export-gmaptiles', _("(experimental) ") + _('Export GMap Tiles'),
+ export_gmap_dialog, helptext=_('Export as Google Map tiles')))
+
+# 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('export-gmaptiles')
+
Added: trunk/thuban/Extensions/gMapTiles/exporter.py
===================================================================
--- trunk/thuban/Extensions/gMapTiles/exporter.py 2008-07-30 03:18:02 UTC (rev 2860)
+++ trunk/thuban/Extensions/gMapTiles/exporter.py 2008-07-30 05:52:31 UTC (rev 2861)
@@ -0,0 +1,161 @@
+import os
+import math
+import mapscript
+from Extensions.umn_mapserver.mf_export import thuban_to_map
+from Extensions.umn_mapserver.mapfile import MF_Map
+from Thuban.Model.proj import Projection
+try:
+ from PythonMagick import Image
+ pythonMagick = True
+except:
+ print "Coultn't import PythonMagick. gMapTile will call convert instead."
+ pythonMagick = False
+
+cmd = 'convert -crop 256x256+%d+%d %s/%d/model.png %s/%d/%d.%d.png'
+
+T = 2048 # Big Tile size
+
+# Port to Python of Google Map's javascript GMercatorClass
+class Mercator (object):
+ def __init__(self, zoom):
+ self.tileSize = 256
+ self.zoom = zoom
+ self.tiles = 2 ** zoom
+ self.circumference = self.tileSize * self.tiles
+ self.semi = self.circumference / 2
+ self.radius = self.circumference / (2 * math.pi)
+ self.fE = -1.0 * self.circumference / 2.0
+ self.fN = self.circumference / 2.0
+
+ def longToX (self, degrees):
+ longitude = math.radians(degrees + 180)
+ return (self.radius * longitude)
+
+ def latToY (self, degrees):
+ latitude = math.radians(degrees)
+ y = self.radius/2.0 * math.log((1.0 + math.sin(latitude)) /
+ (1.0 - math.sin(latitude)))
+ return self.fN - y
+
+ def xToLong (self, x):
+ longRadians = x/self.radius
+ longDegrees = math.degrees(longRadians) - 180
+ rotations = math.floor((longDegrees + 180)/360)
+ longitude = longDegrees - (rotations * 360)
+ return longitude
+
+ def yToLat (self, y):
+ y = self.fN - y
+ latitude = ((math.pi / 2) -
+ (2 * math.atan(math.exp(-1.0 * y / self.radius))))
+ return math.degrees(latitude)
+
+def generate (mf, pixtents, zoom, folder):
+ """ Generate an image of the map, and then
+ Chop up in to 256x256px tiles. 'mf' is the mapObj object completely
+ configured except for its size and extents.
+ 'pixtents' is the extents to be shown, in projected (pixel)
+ coordinates.
+ 'zoom' is the zoom level for the image.
+ 'folder' is the folder to save the tiles. """
+ c = Mercator(zoom)
+ extents = [pixtents[0] - c.semi, c.semi - pixtents[1],
+ pixtents[2] - c.semi, c.semi - pixtents[3]]
+ mf.set_size (pixtents[2] - pixtents[0], pixtents[1] - pixtents[3])
+ mf.set_extent (extents)
+ img = mf._mf_map.draw()
+ png = img.save(folder+'/%d/model.png' % (zoom,))
+ xTile = pixtents[0] // 256
+ yTile = pixtents[3] // 256
+ width = pixtents[2] - pixtents[0]
+ height = pixtents[1] - pixtents[3]
+ i = xTile
+ x = 0
+ if pythonMagick:
+ img = Image(str(folder+"/%d/model.png" % (zoom,)))
+ while x < width:
+ y = 0
+ j = yTile+1
+ while y < height:
+ if pythonMagick:
+ img2 = Image(img)
+ img2.crop ('256x256+%d+%d' % (x, y))
+ img2.write (str(folder+"/%d/%d.%d.png" % (zoom, i, j)))
+ else:
+ c = cmd % (x, y, folder, zoom, folder, zoom, i, j)
+ os.system(c)
+ y += 256
+ j += 1
+ x += 256
+ i += 1
+
+def export (context, minZoom, maxZoom, dataFolder, filename=None):
+ """ Use the Thuban context to render the current map to gMapTiles.
+ All zoom levels between 'minZoom' and 'maxZoom' will be generated.
+ Tiles will be saved to 'dataFolder'.
+ If 'filename' is not None, the map will be generated from this mapfile,
+ instead of using 'context' (for testing only). """
+ if filename is None:
+ mf = MF_Map(mapscript.mapObj(""))
+ mf.set_size (60, 50) # Set some size for thuban_to_map not to fail
+ thuban_to_map (context, mf)
+ else:
+ mf = MF_Map(mapscript.mapObj(filename))
+ # We need all layers to have a projection, assume that they're
+ # using un-projected (lat/long) coordinates. Probably an exception
+ # should be raised instead
+ for lindex in range(mf._mf_map.numlayers):
+ l = mf._mf_map.getLayer(lindex)
+ if l.getProjection() == '':
+ l.setProjection("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs")
+ mf.set_imagetype ('png')
+ mf._mf_map.imagecolor.setRGB(234, 234, 234)
+ mf._mf_map.transparent = 1
+ of = mapscript.outputFormatObj("GD/png")
+ of.transparent = 1
+ mf._mf_map.setOutputFormat (of)
+
+ if filename is None:
+ extents = context.mainwindow.canvas.map.BoundingBox() # Unprojected
+ else:
+ extents = mf.get_extent().get_rect()
+ if extents is None:
+ return
+ minx, miny, maxx, maxy = extents
+ for zoom in range(minZoom, maxZoom + 1):
+ try:
+ os.makedirs (os.path.join(dataFolder, str(zoom)))
+ except OSError:
+ pass # Lets assume that the directory already exists
+ coord = Mercator(zoom)
+ gMercator = ["proj=merc", "a=%f"%coord.radius, "b=%f"%coord.radius]
+ mf.set_projection (Projection(gMercator))
+ mminx = ((int(coord.longToX(minx))) // 256) * 256 # Projected (pixels)
+ mmaxx = ((int(coord.longToX(maxx))) // 256) * 256
+ mmaxy = ((int(coord.latToY(maxy)) + 255)// 256) * 256 - 1
+ mminy = ((int(coord.latToY(miny)) + 255)// 256) * 256 - 1
+ width = mmaxx - mminx
+ height = mminy - mmaxy
+ width = max (width, 1)
+ height = max (height, 1)
+ if width <= T and height < T:
+ pixtents = [mminx, mminy, mmaxx, mmaxy]
+ generate (mf, pixtents, zoom, dataFolder)
+ else:
+ x = mminx
+ while x < mmaxx:
+ y = mmaxy
+ while y < mminy:
+ pixtents = [x, min(y + T - 1, mminy), min(x + T - 1, mmaxx), y]
+ generate (mf, pixtents, zoom, dataFolder)
+ y += T
+ x += T
+
+if __name__ == '__main__':
+ import sys
+ if len(sys.argv) <= 1:
+ print "-----\nUsage: python exporter.py <mapfile.map>"
+ else:
+ # Say, let's export only for zoom level seven. I like seven.
+ export (None, 7, 7, 'tiles', sys.argv[-1])
+
Added: trunk/thuban/Extensions/gMapTiles/sample/README.txt
===================================================================
--- trunk/thuban/Extensions/gMapTiles/sample/README.txt 2008-07-30 03:18:02 UTC (rev 2860)
+++ trunk/thuban/Extensions/gMapTiles/sample/README.txt 2008-07-30 05:52:31 UTC (rev 2861)
@@ -0,0 +1,20 @@
+This is a sample application of the gMapTiles extension. To test this sample
+you need to:
+
+ * Use thuban to generate tiles for some data.
+
+ * Make tile.py accessible via web on your web server, and make sure that it has
+ execution permissions. Let's say that the URL to tile.py is now
+ http://yourserver/tile.py. If you open this URL in a web browser you should
+ see the message "Missing input arguments"
+
+ * Edit tile.py and replace the PATH_TO_TILES string constant for the absolute
+ path where you saved the tiles.
+
+ * Make sample.html accessible via web on your web server. Edit sample.html
+ to use your own Google Maps API Key, and replace the URL_TO_TILE_PY string
+ constant with tile.py's absolute URL. (make sure your key is valid for the
+ sample's URL, more info, http://code.google.com/apis/maps/documentation/ )
+
+Enjoy!
+
Added: trunk/thuban/Extensions/gMapTiles/sample/sample.html
===================================================================
--- trunk/thuban/Extensions/gMapTiles/sample/sample.html 2008-07-30 03:18:02 UTC (rev 2860)
+++ trunk/thuban/Extensions/gMapTiles/sample/sample.html 2008-07-30 05:52:31 UTC (rev 2861)
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
+ <title>Google Map Tiles Proof of concept</title>
+ <script src="http://maps.google.com/maps?file=api&v=2.x&key=[Insert your Google Maps API Key here]"
+ type="text/javascript">
+ </script>
+ <script type="text/javascript">
+
+var URL_TO_TILE_PY = "http://yourserver/tile.py";
+
+var map;
+
+function load() {
+ // Set up the initial map state.
+ if(document.implementation.hasFeature(
+ "http://www.w3.org/TR/SVG11/feature#SVG","1.1")){
+ _mSvgEnabled = true;
+ _mSvgForced = true;
+ }
+ map = new GMap2(document.getElementById("map"));
+ map.addControl(new GLargeMapControl());
+ map.addControl(new GMapTypeControl());
+ var omc = new GOverviewMapControl();
+ map.addControl(omc);
+ map.setCenter(new GLatLng(-42.423457,-60.292969),4);
+
+ var tilelayer = new GTileLayer(new GCopyrightCollection(), 0, 17);
+ tilelayer.getTileUrl = function(tile, zoom) {
+ var url = URL_TO_TILE_PY + "?x="+tile.x+"&y="+tile.y+"&z="+zoom;
+ //alert ("Called getTileUrl ("+tile.x+"/"+tile.y+", "+zoom+")\nReturning: " + url);
+ return url;
+ };
+ tilelayer.getOpacity = function() {return 0.6;}
+ map.addOverlay(new GTileLayerOverlay(tilelayer));
+
+}
+
+ </script>
+</head>
+<body onload="load()" onunload="GUnload()">
+<h2>The Map</h2>
+<div id="map" style="width: 800px; height: 500px"></div>
+</body>
+</html>
+
Added: trunk/thuban/Extensions/gMapTiles/sample/tile.py
===================================================================
--- trunk/thuban/Extensions/gMapTiles/sample/tile.py 2008-07-30 03:18:02 UTC (rev 2860)
+++ trunk/thuban/Extensions/gMapTiles/sample/tile.py 2008-07-30 05:52:31 UTC (rev 2861)
@@ -0,0 +1,32 @@
+#!/usr/bin/python
+import cgi
+import cgitb
+cgitb.enable()
+import cStringIO
+
+PATH_TO_TILES = "/path/to/tiles"
+
+size = 256 #image width and height
+
+def getTile(x, y, z):
+ z = int(z)
+ y = int(y)
+ x = int(x)
+
+ try:
+ f = open('%s/%d/%d.%d.png' % (PATH_TO_TILES, z, x, y))
+ except IOError:
+ print "Content-type: text/plain\n\nNothing"
+ return
+
+ print "Content-type: image/png\n"
+ print f.read()
+
+if __name__ == "__main__":
+ form = cgi.FieldStorage()
+ if "x" in form and "y" in form and "z" in form:
+ getTile(form["x"].value, form["y"].value, form["z"].value)
+ else:
+ print "Content-type: text/html\n"
+ print """<html><body>Missing input arguments</body></html>"""
+
Property changes on: trunk/thuban/Extensions/gMapTiles/sample/tile.py
___________________________________________________________________
Name: svn:executable
+ *
Added: trunk/thuban/Extensions/gMapTiles/test/test_gMapTileExport.py
===================================================================
--- trunk/thuban/Extensions/gMapTiles/test/test_gMapTileExport.py 2008-07-30 03:18:02 UTC (rev 2860)
+++ trunk/thuban/Extensions/gMapTiles/test/test_gMapTileExport.py 2008-07-30 05:52:31 UTC (rev 2861)
@@ -0,0 +1,59 @@
+import unittest
+import os, sys
+from Thuban.Model.layer import Layer
+from Thuban.Model.map import Map
+from Thuban.Model.session import Session
+from Thuban.UI.context import Context
+from Thuban.Lib.connector import ConnectorError
+
+mapscriptAvailable=True
+try:
+ import mapscript
+ from Extensions.gMapTiles.exporter import export
+except ImportError:
+ mapscriptAvailable=False
+
+def rm_rf(d):
+ for path in (os.path.join(d,f) for f in os.listdir(d)):
+ if os.path.isdir(path):
+ rm_rf(path)
+ else:
+ os.unlink(path)
+ os.rmdir(d)
+
+class DummyMainWindow(object):
+ def __init__(self, canvas):
+ self.canvas = canvas
+
+class DummyCanvas(object):
+ def __init__(self, map):
+ self.map = map
+ def Map(self):
+ return self.map
+ def VisibleExtent (self):
+ return self.map.BoundingBox()
+
+class TestGMapTileExport(unittest.TestCase):
+ def setUp(self):
+ self.session = Session("A Session")
+ self.map = Map("A Map")
+ self.session.AddMap(self.map)
+ shapefile = "../../../Data/iceland/political.shp"
+ self.store = self.session.OpenShapefile(shapefile)
+ layer = Layer("A Layer", self.store)
+ self.map.AddLayer(layer)
+
+ def testExport(self):
+ tileDir = 'temp'
+ if not mapscriptAvailable:
+ #Skip test...
+ return
+ mainwindow = DummyMainWindow(DummyCanvas(self.map))
+ context = Context(None, self.session, mainwindow)
+ export (context, 7, 7, tileDir)
+ self.session.Destroy()
+ rm_rf(tileDir)
+
+if __name__ == "__main__":
+ unittest.main()
+
Modified: trunk/thuban/Thuban/thuban_cfg.py
===================================================================
--- trunk/thuban/Thuban/thuban_cfg.py 2008-07-30 03:18:02 UTC (rev 2860)
+++ trunk/thuban/Thuban/thuban_cfg.py 2008-07-30 05:52:31 UTC (rev 2861)
@@ -35,6 +35,11 @@
print x
try:
+ import Extensions.gMapTiles
+except Exception, x:
+ print x
+
+try:
import Extensions.mouseposition
except Exception, x:
print x
More information about the Thuban-commits
mailing list