#! /bin/env python
###################################################################################
# #
# Stitch v3.0, #
# http://www.jportsmouth.com/code/Stitch/stitch.html #
# Copyright (C) 2009-2010 Jamie Portsmouth (jamports@mac.com) #
# Multithreading contributed by Morgan Tørvolt (morgan@torvolt.com) #
# #
# Stitch is a Python script to assemble large Google maps. A rectangle of #
# latitude and longitude is specified, together with a desired number of pixels #
# along the long edge. The appropriate tiles are then automatically downloaded #
# and stitched together into a single map. #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see . #
# #
###################################################################################
import sys
import os
import urllib
import urllib2
import math
import wx
import wx.html
import threading
import Queue
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFilter
from PIL import ImageMath
#########################################################################################################
# Current Google map URLS
# If the script reports that map URLS are invalid, replace the code section below
# with the updated version from http://www.jportsmouth.com/code/Stitch/stitch.html
##################### start of map URL code section ###################################################
NRM_URL = "http://mt0.google.com/vt/lyrs=m@118&hl=en&src=api&x=0&y=0&z=0&s="
SAT_URL = "http://khm0.google.com/kh/v=54&cookie=fzwq1C7TB0nUYgDs9ekB-1G3k3HolraYN4ITsQ&x=0&y=0&z=0&s="
PHY_URL = "http://mt0.google.com/vt/v=app.118&hl=en&src=api&x=0&y=0&z=0&s="
SKY_URL = "http://mw1.google.com/mw-planetary/sky/skytiles_v1/0_0_0.jpg"
###################### end of map URL code section ####################################################
# Queue. We drop all the urls in this queue
grabPool = Queue.Queue( 0 )
# Background threads. We start a few of these
class ThreadingClass( threading.Thread ):
def __init__(self):
self._stopevent = threading.Event()
threading.Thread.__init__(self)
def join(self, timeout=None):
self._stopevent.set()
threading.Thread.join(self, timeout)
def run( self ):
self.serverSelectCounter = 0
# Run until termination event. After filling the queue, we can just wait until the queue is empty.
while not self._stopevent.isSet():
try:
tile = grabPool.get(True, 0.001)
except:
continue
url = tile[0]
output = './tiles/tile_' + tile[1] + '.jpg'
# Fixing up url for servers that had %s writted into them for load balancing
gotTile = False
if( url.find( "%s" ) != -1 ):
for x in range(4):
url = url % ( self.serverSelectCounter % 4 )
self.serverSelectCounter = self.serverSelectCounter + 1
gotTile = self.download(url, output)
if gotTile: break
# Otherwise url does not need to be fixed up
else:
gotTile = self.download(url, output)
if (gotTile != True):
print "(Map URL " + url + " might be invalid or a server might be down. Visit http://www.jportsmouth.com/code/Stitch/stitch.html and update the map URL code section)"
grabPool.task_done()
def download( self, url, output ):
try:
urllib.urlretrieve( url, output )
return True
except:
return False
class StitchedMap:
def __init__(self, lat, lon, res, zoom, maptype):
self.lat = lat
self.lon = lon
self.latVal = (float(lat[0]), float(lat[1]))
self.lonVal = (float(lon[0]), float(lon[1]))
if (self.latVal[0] >= self.latVal[1]):
print 'Invalid latitude range. Aborting.'
return
if (self.lonVal[0] >= self.lonVal[1]):
print 'Invalid longitude range. Aborting.'
return
self.res = res
self.zoom = zoom # understood to be -1 if resolution specified
self.maptype = maptype
self.MAP_MODE_PREFIX = self.makeDummyUrl(NRM_URL.split('&')[0])
self.SAT_MODE_PREFIX = self.makeDummyUrl(SAT_URL.split('&')[0])
self.PHY_MODE_PREFIX = self.makeDummyUrl(PHY_URL.split('&')[0])
self.SKY_MODE_PREFIX = SKY_URL.replace('0_0_0.jpg','')
def makeDummyUrl(self, url):
# Some string hacking to replace e.g. "http://mt0.google.com..." with "http://mt%s.google.com..."
# so that later we can replace %s with an integer 0-4 for load balancing
server_url = url.split(".google")
server_name = server_url[0][0:len(server_url[0])-1]
dummy_url = server_name + "%s.google" + server_url[1]
return dummy_url
def generate(self):
c0 = "(" + self.lat[0] + ", " + self.lon[0] + ")"
c1 = "(" + self.lat[1] + ", " + self.lon[1] + ")"
print '\n######################################################################'
print "Making " + self.maptype + " map defined by (lat, lon) corners " + c0 + " and " + c1
EX = math.fabs(float(self.lon[1]) - float(self.lon[0]))
EY = math.fabs(float(self.lat[1]) - float(self.lat[0]))
print 'Requested map (lng, lat) size in degrees is: ', str(EX), str(EY)
# compute which 256x256 tiles we need to download
self.computeTileMatrix()
if (self.zoom<0) or (self.zoom>19):
print 'Invalid zoom level (' + str(self.zoom) + '). Aborting.'
return
print 'Zoom level: ', str(self.zoom)
# Connect to Google maps and download tiles
self.download()
# Finally stitch the downloaded maps together into the final big map
self.stitch()
def computeTileRange(self):
if self.zoom == -1:
# find a zoom level which gives approximately the desired number of pixels along the long edge
EX = math.fabs(float(self.lon[1]) - float(self.lon[0]))
EY = math.fabs(float(self.lat[1]) - float(self.lat[0]))
aspect = 2.0*EY/EX
ntiles_x = 0
ntiles_y = 0
if (EX>EY):
self.ntiles_x = long( float(self.res)/256 + 1 )
self.ntiles_y = long( aspect*float(self.res)/256 + 1 )
else:
self.ntiles_y = long( float(self.res)/256 + 1 )
self.ntiles_x = long( float(self.res)/(aspect*256) + 1 )
log2of10 = 3.321928094887362
self.zoom = log2of10 * math.log10( max(self.ntiles_x, self.ntiles_y) * 360.0/max(EX, EY) )
self.zoom = long(self.zoom)
# In satellite mode, the zoom level in the html query goes from 0 to 14 inclusive,
# 0 being the lowest res (i.e. the map of the world).
# In the other modes, the zoom level goes from -2 to 17 inclusive, 17 being the map of the world.
if (self.maptype != 'satellite'):
self.htmlzoom = 17 - self.zoom
# Google maps uses the Mercator projection, so we need to convert the given latitudes
# into Mercator y-coordinates. Google takes the vertical edges of the map to be at
# y = +/-pi, corresponding to latitude +/-85.051128.
# It is convenient therefore to compute y/2 for each latitude. We can then
# just use the y coord as if it were a latitude, with the top edges at +/-90.0 "degrees".
l0 = self.latVal[0]
l1 = self.latVal[1]
self.yVal = (self.latitudeToMercator(l0), self.latitudeToMercator(l1))
# get the corner tile
tileA = self.getTile(self.lonVal[0], self.yVal[0])
tileB = self.getTile(self.lonVal[1], self.yVal[1])
return [tileA, tileB]
# Allow phi in range [-90.0, 90.0], return in same range
def latitudeToMercator(self, phi):
# If the given latitude falls outside of the +/-85.051128 range, we clamp it back into range.
phimax = 85.05112
if phi>phimax: phi = phimax
elif phi<-phimax: phi = -phimax
# find sign
sign = 0.0
if phi>=0.0: sign = 1.0
else: sign = -1.0
# convert to rad
phi *= math.pi/180.0
# make positive for Mercator formula
phi = math.fabs(phi)
# find [0,pi] range Mercator coords
y = math.log( math.tan(phi) + 1.0/math.cos(phi) )
# put back sign and scale by factor of 2
y *= 0.5*sign
# convert to degrees
y *= 180.0/math.pi
# clamp to [-90.0, 90.0]
if y>90.0: y = 90.0
elif y<-90.0: y = -90.0
return y
def computeTileMatrix(self):
tileRange = self.computeTileRange()
tileA = tileRange[0]
tileB = tileRange[1]
tileAstr = '(' + str(tileA[0]) + ',' + str(tileA[1]) + ')'
tileBstr = '(' + str(tileB[0]) + ',' + str(tileB[1]) + ')'
print 'Corner tile indices: ' + tileAstr + ', ' + tileBstr
self.nX = abs(tileB[0] - tileA[0]) + 1
self.nY = abs(tileB[1] - tileA[1]) + 1
print 'Total number of tiles to download: ' + str(self.nX*self.nY)
# Make a nX*nY matrix of the tiles (i,j) we need, with (0,0) in the lower-left.
# The google tile indices (lng, lat) corresponding to (i,j) (at the given zoom level) are stored
# in each tile.
# We need the fact that in satellite mode, the lng, lat tile indices increase with both longitude
# and latitude, but in the other modes, the lat index decreases with latitude
self.tiles = []
for i in range(0, self.nX):
lng = tileA[0] + i
column = []
for j in range(0, self.nY):
lat = 0
if self.maptype == 'satellite':
lat = tileA[1] + j
code = self.genSatelliteTileCode(lng, lat)
else:
lat = tileA[1] - j
code = ''
status = True
tile = [lng, lat, code, status]
column.append(tile)
self.tiles.append(column)
def checkURL(self, url):
try:
urllib2.urlopen(url).read()
except:
return False
return True
def download(self):
if os.path.exists("./tiles") != True:
os.mkdir("./tiles")
print ''
n = 1
for column in self.tiles:
for tile in column:
tilePath = './tiles/tile_' + self.makeIdentifier(tile) + '.jpg'
# If the tile with the expected identifier suffix already exists in the tiles directory,
# assume that is the one we want (allows execution to continue later if interrupted).
if os.path.exists(tilePath):
print 'Using existing tile ' + str(n) + '/' + str(self.nX*self.nY) + (
', (i, j) = (' + str(tile[0]) + ',' + str(tile[1]) + ')' )
else:
mapurl = ''
if self.maptype == 'map': mapurl = self.gen_MAP_URL(tile)
elif self.maptype == 'satellite': mapurl = self.gen_SAT_URL(tile)
elif self.maptype == 'terrain': mapurl = self.gen_PHY_URL(tile)
elif self.maptype == 'sky': mapurl = self.gen_SKY_URL(tile)
else:
print 'Unknown map type! Quitting. Humph'
sys.exit()
if mapurl:
print 'Downloading tile ' + str(n) + '/' + str(self.nX*self.nY) + ', (i, j) = (' + str(tile[0]) + ',' + str(tile[1]) + ')'
grabPool.put( [ mapurl, self.makeIdentifier(tile) ] )
else:
print 'Tile ' + str(n) + '/' + str(self.nX*self.nY) + (
', (i, j) = (' + str(tile[0]) + ',' + str(tile[1]) + ') is not stored by Google, and will be rendered black')
tile[3] = False
n += 1
grabPool.join()
def makeIdentifier(self, tile):
identifier = self.maptype + '_' + str(self.zoom) + '_'
if self.maptype == 'satellite':
identifier += tile[2]
else:
identifier += str(tile[0]) + '_' + str(tile[1])
return identifier
def getTile(self, lng, lat):
nTile = 1 << self.zoom
# note, assume ranges are lng = (-180,180), lat = (-90,90)
tilex = long(nTile * (float(lng) + 180.0)/360.0)
tiley = long(nTile * (float(lat) + 90.0 )/180.0)
if tilex == nTile: tilex -= 1
if tilex<0: tilex = 0
if tiley == nTile: tiley -= 1
if tiley<0: tiley = 0
# the hybrid and terrain modes index the tiles descending with latitude
if self.maptype != 'satellite':
tiley = nTile - 1 - tiley
tile = (tilex, tiley)
return tile
def gen_MAP_URL(self, tile):
x = str(tile[0])
y = str(tile[1])
url = self.MAP_MODE_PREFIX + '&x=' + x + '&y=' + y + '&zoom=' + str(self.htmlzoom)
return url
def gen_SAT_URL(self, tile):
code = tile[2]
url = self.SAT_MODE_PREFIX + '&t=' + code
return url
def gen_PHY_URL(self, tile):
x = str(tile[0])
y = str(tile[1])
url = self.PHY_MODE_PREFIX + '&x=' + x + '&y=' + y + '&zoom=' + str(self.htmlzoom)
return url
def gen_SKY_URL(self, tile):
x = str(tile[0])
y = str(tile[1])
url = self.SKY_MODE_PREFIX + x + '_' + y + '_' + str(self.zoom) + '.jpg'
return url
def convertToBinary(self, x, n):
b = ''
for i in range(0,n):
b = str((x >> i) & 1) + b
return b
def genSatelliteTileCode(self, x, y):
# In satellite mode, the tiles are indexed by a sequence of the letters q, r, s, t, where
# there are 4^zoom tiles to index at each level. This works as indicated below:
#
# zoom 0 zoom1 zoom 2 etc...
#
# t tq tr tqq tqr trq trr
# tt ts tqt tqs trt trs
#
# ttq ttr tsq tsr
# ttt tts tst tss
nTile = 1 << self.zoom
if ((y < 0) or (nTile-1 < y)):
return 'x'
if ((x < 0) or (nTile-1 < x)):
x = x % nTile
if (x < 0):
x += nTile;
c = 't'
# convert each to zoom-digit binary representation
bx = self.convertToBinary(x, self.zoom)
by = self.convertToBinary(y, self.zoom)
# q r s t
# left(0)/right(1) (x) 0 1 1 0
# down(0)/up(1) (y) 1 1 0 0
for i in range(0, self.zoom):
if (bx[i] == '0'):
if(by[i] == '0'):
c += 't'
else:
c += 'q'
else:
if(by[i] == '0'):
c += 's'
else:
c += 'r'
return c
def getCoordsOfTile(self, tile):
nTile = 1 << self.zoom
width = 360.0/float(nTile)
height = 180.0/float(nTile)
tiley = tile[1]
if self.maptype != 'satellite':
tiley = nTile - 1 - tiley
X = -180.0 + float(tile[0]) * width
Y = -90.0 + float(tiley) * height
# coords of corners of tile
LL = (X, Y)
UR = (X+width, Y+height)
return [LL, UR]
def crop(self, Map):
# Crop off the excess space.
# Get (lat, lon) in degrees of corners of image
tileA = self.tiles[0][0]
coordsA = self.getCoordsOfTile(tileA)
tileB = self.tiles[self.nX-1][self.nY-1]
coordsB = self.getCoordsOfTile(tileB)
LL = (coordsA[0][0], coordsA[0][1])
UR = (coordsB[1][0], coordsB[1][1])
# (ax, ay) and (bx, by) are the image coords of the corners of the desired map:
ax = (self.lonVal[0] - LL[0]) / (UR[0] - LL[0])
ay = (self.yVal[0] - LL[1]) / (UR[1] - LL[1])
bx = (self.lonVal[1] - LL[0]) / (UR[0] - LL[0])
by = (self.yVal[1] - LL[1]) / (UR[1] - LL[1])
ax = int(self.pX * ax)
ay = int(self.pY * (1.0-ay))
bx = int(self.pX * bx)
by = int(self.pY * (1.0-by))
#clamp to be safe
if ax>=self.pX: ax=self.pX-1;
if ax<0: ax=0;
if bx>=self.pX: bx=self.pX-1;
if bx<0: bx=0;
if ay>=self.pY: ay=self.pY-1;
if ay<0: ay=0;
if by>=self.pY: by=self.pY-1;
if by<0: by=0;
box = [ax, by, bx, ay]
return Map.crop(box)
def stitch(self):
print '\nStitching tiles'
self.pX = 256 * self.nX
self.pY = 256 * self.nY
mode = "RGB"
Map = Image.new(mode, (self.pX, self.pY))
for i in range(0, self.nX):
for j in range(0, self.nY):
tile = self.tiles[i][j]
if tile[3] == False:
continue
path = './tiles/tile_' + self.makeIdentifier(tile) + '.jpg'
# pixel coords of top left corner of this tile
cX = 256 * i
cY = self.pY - 256 * (j+1)
im = Image.open(path)
Map.paste(im, (cX, cY))
cropMap = self.crop(Map)
# give the map file a semi-unique name, derived from the lower-left tile coords
mappath = './stitched_' + self.makeIdentifier(self.tiles[0][0]) + '.jpg'
cropMap.save(mappath)
print 'Saved stitched map ' + mappath
print 'Finished.'
############################ wxPython GUI interface ############################
# Frame dimensions
wX = 360
wY = 430
# border width
bW = 20
# coord panel height
hY = 180
class MainPanel(wx.Panel):
def OnSetFocus(self, evt):
print "OnSetFocus"
evt.Skip()
def OnKillFocus(self, evt):
print "OnKillFocus"
evt.Skip()
def OnWindowDestroy(self, evt):
print "OnWindowDestroy"
evt.Skip()
def __init__(self, parent, id):
self.parent = parent
pos = wx.Point(bW,bW)
size = wx.Size(wX-2*bW, hY)
hspace = 4
wx.Panel.__init__(self, parent, -1, pos, size)
# Lat/Lng direct entry section
heading_LL = wx.StaticText(self, -1, "Lower left")
heading_UR = wx.StaticText(self, -1, "Upper right")
fW = 125
lat_label = wx.StaticText(self, -1, "Latitude")
lon_label = wx.StaticText(self, -1, "Longitude")
self.latLL_text = wx.TextCtrl(self, -1, "-90.0", size=(fW, -1))
self.latLL_text.SetInsertionPoint(0)
self.Bind( wx.EVT_TEXT, self.EvtTextChanged, self.latLL_text)
self.lonLL_text = wx.TextCtrl(self, -1, "-180.0", size=(fW, -1))
self.lonLL_text.SetInsertionPoint(0)
self.Bind( wx.EVT_TEXT, self.EvtTextChanged, self.lonLL_text)
self.latUR_text = wx.TextCtrl(self, -1, "90.0", size=(fW, -1))
self.latUR_text.SetInsertionPoint(0)
self.Bind( wx.EVT_TEXT, self.EvtTextChanged, self.latUR_text)
self.lonUR_text = wx.TextCtrl(self, -1, "180.0", size=(fW, -1))
self.lonUR_text.SetInsertionPoint(0)
self.Bind( wx.EVT_TEXT, self.EvtTextChanged, self.lonUR_text)
coord_sizer = wx.FlexGridSizer(cols=3, hgap=4*hspace, vgap=2*hspace)
coord_sizer.AddMany([ (0, 0), heading_LL, heading_UR,
lat_label, self.latLL_text, self.latUR_text,
lon_label, self.lonLL_text, self.lonUR_text,
(0, 0), (0,0), (0,0) ])
# Lat/Lng code entry section
code_label = wx.StaticText(self, -1, "Code: ")
self.coordCode = wx.TextCtrl(self, -1, "", size=(fW*2, -1))
self.coordCode.SetInsertionPoint(0)
self.Bind( wx.EVT_TEXT, self.EvtTextChanged, self.coordCode)
useCode_cb = wx.CheckBox(self, -1, "Use code?", wx.DefaultPosition)
self.Bind( wx.EVT_CHECKBOX, self.EvtCoordCheckBox, useCode_cb)
self.useCode = False
code_sizer = wx.FlexGridSizer(cols=2, hgap=3*hspace, vgap=3*hspace)
code_sizer.AddMany([ useCode_cb, (0,0),
code_label, self.coordCode,
(0, 0), (0,0) ])
# 'Specify resolution' option enable checkbox
self.useRes_rb = wx.RadioButton(self, -1, "Specify resolution", wx.DefaultPosition)
self.useRes_rb.SetValue(True)
self.Bind( wx.EVT_RADIOBUTTON, self.EvtResolutionRadioButton, self.useRes_rb)
self.useResolution = True
res_label = wx.StaticText(self, -1, "Approx. number of pixels: ")
self.res_text = wx.TextCtrl(self, -1, "512", size=(fW/2, -1))
self.res_text.SetInsertionPoint(0)
self.Bind( wx.EVT_TEXT, self.EvtTextChanged, self.res_text)
res_sizer = wx.FlexGridSizer(cols=1, hgap=3*hspace, vgap=hspace)
res_sizer.AddMany([ self.useRes_rb, (0,0) ])
res_sizer = wx.FlexGridSizer(cols=2, hgap=3*hspace, vgap=3*hspace)
res_sizer.AddMany([ self.useRes_rb, (0,0),
res_label, self.res_text,
(0, 0), (0,0) ])
# 'Specify zoom level' option enable checkbox and entry
self.useZoom_rb = wx.RadioButton(self, -1, "Specify zoom level", wx.DefaultPosition)
self.useZoom_rb.SetValue(False)
self.Bind( wx.EVT_RADIOBUTTON, self.EvtZoomRadioButton, self.useZoom_rb)
self.useZoomLevel = False
self.zoomInfo_label = wx.StaticText(self, -1, "(lowest = 0, highest = 19)")
zoom_label = wx.StaticText(self, -1, "Zoom level: ")
self.zoomLevel_text = wx.TextCtrl(self, -1, "5", size=(fW/2, -1))
self.zoomLevel_text.SetInsertionPoint(0)
self.Bind( wx.EVT_TEXT, self.EvtTextChanged, self.zoomLevel_text)
self.zoomLevel_text.Enable(False)
zoom_sizer = wx.FlexGridSizer(cols=2, hgap=3*hspace, vgap=3*hspace)
zoom_sizer.AddMany([ self.useZoom_rb, self.zoomInfo_label,
zoom_label, self.zoomLevel_text,
(0, 0), (0,0) ])
# Map type selection radio box
self.radioList = ['map', 'satellite', 'terrain', 'sky']
rb = wx.RadioBox(self, -1, "Map type", wx.DefaultPosition, wx.DefaultSize,
self.radioList, 3, wx.RA_SPECIFY_COLS)
self.Bind( wx.EVT_RADIOBOX, self.EvtRadioBox, rb)
self.maptype = 'map'
rbsizer = wx.BoxSizer(wx.HORIZONTAL)
rbsizer.Add(rb, 0, wx.GROW|wx.ALL, hspace)
# Tiles info
self.tilesInfo_label = wx.StaticText(self, -1, '', size=(fW, -1))
ziFont = wx.Font(10, wx.NORMAL, wx.NORMAL, wx.BOLD, False, u'Courier')
self.tilesInfo_label.SetFont(ziFont)
# Run button
b = wx.Button(self, -1, "Run")
self.Bind(wx.EVT_BUTTON, self.OnRun, b)
bsizer = wx.BoxSizer(wx.HORIZONTAL)
bsizer.Add(b, 0, wx.GROW|wx.ALL, hspace)
# UI layout
border = wx.BoxSizer(wx.VERTICAL)
border.Add(coord_sizer, 0, wx.GROW)
border.Add(code_sizer, 0, wx.GROW)
border.Add(res_sizer, 0, wx.GROW)
border.Add(zoom_sizer, 0, wx.GROW)
border.Add(rbsizer, 0, wx.GROW)
border.AddSpacer(15)
border.Add(self.tilesInfo_label, 0, wx.GROW)
border.AddSpacer(5)
border.Add(bsizer, 0, wx.GROW)
self.SetSizer(border)
self.SetAutoLayout(True)
border.Fit(self)
self.updateMapParams()
def updateMapParams(self):
lat = None
lon = None
if self.useCode == True:
coords = self.coordCode.GetValue().split('_')
if len(coords) != 4:
print 'Code cannot be parsed into coordinates, unable to generate map.'
return False
# Ensure that the 0th corner is lower left, even if the user didn't make it so
try:
lat = (coords[1], coords[3])
if float(lat[1]) < float(lat[0]):
lat = (coords[3], coords[1])
lon = (coords[0], coords[2])
if float(lon[1]) < float(lon[0]):
lon = (coords[2], coords[0])
except:
print 'Code cannot be parsed into coordinates, unable to generate map.'
return False
else:
# Ensure that the 0th corner is lower left, even if the user didn't make it so
lat = (self.latLL_text.GetValue(), self.latUR_text.GetValue())
try:
if float(lat[1]) < float(lat[0]):
lat = (self.latUR_text.GetValue(), self.latLL_text.GetValue())
lon = (self.lonLL_text.GetValue(), self.lonUR_text.GetValue())
if float(lon[1]) < float(lon[0]):
lon = ( self.lonUR_text.GetValue(), self.lonLL_text.GetValue())
except:
print 'Invalid longitude/latitude values, unable to generate map.'
return False
zoomLevel = -1
if self.useZoom_rb.GetValue():
try:
zoomLevel = int(self.zoomLevel_text.GetValue())
except:
pass
res = 0
if self.useRes_rb.GetValue():
try:
res = int(self.res_text.GetValue())
except:
pass
self.gmap = StitchedMap(lat, lon, res, zoomLevel, self.maptype)
tileRange = self.gmap.computeTileRange()
tileA = tileRange[0]
tileB = tileRange[1]
nX = abs(tileB[0] - tileA[0]) + 1
nY = abs(tileB[1] - tileA[1]) + 1
tileinfo = ' Will download ' + str(nX*nY) + ' tiles: (' + str(tileA[0]) + ',' + str(tileA[1]) + ') to (' + str(tileB[0]) + ',' + str(tileB[1]) + ')'
self.tilesInfo_label.SetLabel(tileinfo)
return True
def OnRun(self, evt):
if self.updateMapParams():
self.gmap.generate()
def EvtRadioBox(self, event):
maptype = ''
i = event.GetInt()
if i == 0: self.maptype = 'map'
elif i == 1: self.maptype = 'satellite'
elif i == 2: self.maptype = 'terrain'
else: self.maptype = 'sky'
self.updateMapParams()
def EvtCoordCheckBox(self, event):
self.useCode = event.Checked()
if self.useCode:
self.coordCode.Enable(True)
else:
self.coordCode.Enable(False)
self.updateMapParams()
def EvtResolutionRadioButton(self, event):
self.zoomLevel_text.Enable(False)
self.res_text.Enable(True)
self.updateMapParams()
def EvtZoomRadioButton(self, event):
self.zoomLevel_text.Enable(True)
self.res_text.Enable(False)
self.updateMapParams()
def EvtTextChanged(self, event):
if self.useZoom_rb.GetValue():
zoomLevel = 0
try:
zoomLevel = int(self.zoomLevel_text.GetValue())
except:
pass
minZoom = 0
maxZoom = 19
if (zoomLevelmaxZoom):
zoomLevel = maxZoom
self.zoomLevel_text.SetValue(str(zoomLevel))
self.updateMapParams()
class MainWindow(wx.Frame):
def __init__(self, parent, id, title):
N = 10
print "*************** Stitch v3.0 ***************"
print "Starting " + str(N) + " download threads"
self.threads = []
for x in range(N):
thread = ThreadingClass()
thread.start()
self.threads.append(thread)
wx.Frame.__init__(self, parent, wx.ID_ANY, title, wx.DefaultPosition, wx.Size(wX, wY))
controlPanel = MainPanel(self, -1)
def __del__(self):
for thread in self.threads:
thread.join()
print "Terminated download threads. Quitting."
# Entry point
app = wx.PySimpleApp()
frame = MainWindow(None, -1, "Stitch")
frame.Show(True)
app.MainLoop()