Fixed #8113. Made `get_width_height` a `GoogleZoom` method that takes the extent instead of an envelope so it may handle Point geometries. Thanks to Santiago Aguiar for the bug report.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@8428 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Justin Bronn 2008-08-17 21:09:28 +00:00
parent e3ea9ddef5
commit b25d759bed
1 changed files with 34 additions and 37 deletions

View File

@ -6,25 +6,14 @@ from math import pi, sin, cos, log, exp, atan
DTOR = pi / 180. DTOR = pi / 180.
RTOD = 180. / pi RTOD = 180. / pi
def get_width_height(envelope):
# Getting the lower-left, upper-left, and upper-right
# coordinates of the envelope.
ll = Point(envelope[0][0])
ul = Point(envelope[0][1])
ur = Point(envelope[0][2])
height = ll.distance(ul)
width = ul.distance(ur)
return width, height
class GoogleZoom(object): class GoogleZoom(object):
""" """
GoogleZoom is a utility for performing operations related to the zoom GoogleZoom is a utility for performing operations related to the zoom
levels on Google Maps. levels on Google Maps.
This class is inspired by the OpenStreetMap Mapnik tile generation routine This class is inspired by the OpenStreetMap Mapnik tile generation routine
`generate_tiles.py`, and the article "How Big Is the World" (Hack #16) in `generate_tiles.py`, and the article "How Big Is the World" (Hack #16) in
"Google Maps Hacks" by Rich Gibson and Schuyler Erle. "Google Maps Hacks" by Rich Gibson and Schuyler Erle.
`generate_tiles.py` may be found at: `generate_tiles.py` may be found at:
http://trac.openstreetmap.org/browser/applications/rendering/mapnik/generate_tiles.py http://trac.openstreetmap.org/browser/applications/rendering/mapnik/generate_tiles.py
@ -34,25 +23,23 @@ class GoogleZoom(object):
def __init__(self, num_zoom=19, tilesize=256): def __init__(self, num_zoom=19, tilesize=256):
"Initializes the Google Zoom object." "Initializes the Google Zoom object."
# Google's tilesize is 256x256, square tiles are assumed. # Google's tilesize is 256x256, square tiles are assumed.
self._tilesize = tilesize self._tilesize = tilesize
# The number of zoom levels # The number of zoom levels
self._nzoom = num_zoom self._nzoom = num_zoom
# Initializing arrays to hold the parameters for each # Initializing arrays to hold the parameters for each one of the
# one of the zoom levels. # zoom levels.
self._degpp = [] # Degrees per pixel self._degpp = [] # Degrees per pixel
self._radpp = [] # Radians per pixel self._radpp = [] # Radians per pixel
self._npix = [] # 1/2 the number of pixels for a tile at the given zoom level self._npix = [] # 1/2 the number of pixels for a tile at the given zoom level
# Incrementing through the zoom levels and populating the # Incrementing through the zoom levels and populating the parameter arrays.
# parameter arrays.
z = tilesize # The number of pixels per zoom level. z = tilesize # The number of pixels per zoom level.
for i in xrange(num_zoom): for i in xrange(num_zoom):
# Getting the degrees and radians per pixel, and the 1/2 the number of # Getting the degrees and radians per pixel, and the 1/2 the number of
# for every zoom level. # for every zoom level.
self._degpp.append(z / 360.) # degrees per pixel self._degpp.append(z / 360.) # degrees per pixel
self._radpp.append(z / (2 * pi)) # radians per pixl self._radpp.append(z / (2 * pi)) # radians per pixl
self._npix.append(z / 2) # number of pixels to center of tile self._npix.append(z / 2) # number of pixels to center of tile
@ -75,19 +62,18 @@ class GoogleZoom(object):
def lonlat_to_pixel(self, lonlat, zoom): def lonlat_to_pixel(self, lonlat, zoom):
"Converts a longitude, latitude coordinate pair for the given zoom level." "Converts a longitude, latitude coordinate pair for the given zoom level."
# Setting up, unpacking the longitude, latitude values and getting the # Setting up, unpacking the longitude, latitude values and getting the
# number of pixels for the given zoom level. # number of pixels for the given zoom level.
lon, lat = self.get_lon_lat(lonlat) lon, lat = self.get_lon_lat(lonlat)
npix = self._npix[zoom] npix = self._npix[zoom]
# Calculating the pixel x coordinate by multiplying the longitude # Calculating the pixel x coordinate by multiplying the longitude value
# value with with the number of degrees/pixel at the given # with with the number of degrees/pixel at the given zoom level.
# zoom level.
px_x = round(npix + (lon * self._degpp[zoom])) px_x = round(npix + (lon * self._degpp[zoom]))
# Creating the factor, and ensuring that 1 or -1 is not passed in as the # Creating the factor, and ensuring that 1 or -1 is not passed in as the
# base to the logarithm. Here's why: # base to the logarithm. Here's why:
# if fac = -1, we'll get log(0) which is undefined; # if fac = -1, we'll get log(0) which is undefined;
# if fac = 1, our logarithm base will be divided by 0, also undefined. # if fac = 1, our logarithm base will be divided by 0, also undefined.
fac = min(max(sin(DTOR * lat), -0.9999), 0.9999) fac = min(max(sin(DTOR * lat), -0.9999), 0.9999)
# Calculating the pixel y coordinate. # Calculating the pixel y coordinate.
@ -116,18 +102,18 @@ class GoogleZoom(object):
def tile(self, lonlat, zoom): def tile(self, lonlat, zoom):
""" """
Returns a Polygon corresponding to the region represented by a fictional Returns a Polygon corresponding to the region represented by a fictional
Google Tile for the given longitude/latitude pair and zoom level. This Google Tile for the given longitude/latitude pair and zoom level. This
tile is used to determine the size of a tile at the given point. tile is used to determine the size of a tile at the given point.
""" """
# The given lonlat is the center of the tile. # The given lonlat is the center of the tile.
delta = self._tilesize / 2 delta = self._tilesize / 2
# Getting the pixel coordinates corresponding to the # Getting the pixel coordinates corresponding to the
# the longitude/latitude. # the longitude/latitude.
px = self.lonlat_to_pixel(lonlat, zoom) px = self.lonlat_to_pixel(lonlat, zoom)
# Getting the lower-left and upper-right lat/lon coordinates # Getting the lower-left and upper-right lat/lon coordinates
# for the bounding box of the tile. # for the bounding box of the tile.
ll = self.pixel_to_lonlat((px[0]-delta, px[1]-delta), zoom) ll = self.pixel_to_lonlat((px[0]-delta, px[1]-delta), zoom)
ur = self.pixel_to_lonlat((px[0]+delta, px[1]+delta), zoom) ur = self.pixel_to_lonlat((px[0]+delta, px[1]+delta), zoom)
@ -136,24 +122,22 @@ class GoogleZoom(object):
def get_zoom(self, geom): def get_zoom(self, geom):
"Returns the optimal Zoom level for the given geometry." "Returns the optimal Zoom level for the given geometry."
# Checking the input type. # Checking the input type.
if not isinstance(geom, GEOSGeometry) or geom.srid != 4326: if not isinstance(geom, GEOSGeometry) or geom.srid != 4326:
raise TypeError('get_zoom() expects a GEOS Geometry with an SRID of 4326.') raise TypeError('get_zoom() expects a GEOS Geometry with an SRID of 4326.')
# Getting the envelope for the geometry, and its associated width, height # Getting the envelope for the geometry, and its associated width, height
# and centroid. # and centroid.
env = geom.envelope env = geom.envelope
env_w, env_h = get_width_height(env) env_w, env_h = self.get_width_height(env.extent)
center = env.centroid center = env.centroid
for z in xrange(self._nzoom): for z in xrange(self._nzoom):
# Getting the tile at the zoom level. # Getting the tile at the zoom level.
tile = self.tile(center, z) tile_w, tile_h = self.get_width_height(self.tile(center, z).extent)
tile_w, tile_h = get_width_height(tile)
# When we span more than one tile, this is an approximately good # When we span more than one tile, this is an approximately good
# zoom level. # zoom level.
if (env_w > tile_w) or (env_h > tile_h): if (env_w > tile_w) or (env_h > tile_h):
if z == 0: if z == 0:
raise GoogleMapException('Geometry width and height should not exceed that of the Earth.') raise GoogleMapException('Geometry width and height should not exceed that of the Earth.')
@ -162,3 +146,16 @@ class GoogleZoom(object):
# Otherwise, we've zoomed in to the max. # Otherwise, we've zoomed in to the max.
return self._nzoom-1 return self._nzoom-1
def get_width_height(self, extent):
"""
Returns the width and height for the given extent.
"""
# Getting the lower-left, upper-left, and upper-right
# coordinates from the extent.
ll = Point(extent[:2])
ul = Point(extent[0], extent[3])
ur = Point(extent[2:])
# Calculating the width and height.
height = ll.distance(ul)
width = ul.distance(ur)
return width, height