Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #4 and #14. #15

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ Converts a lat,lon to pixel space to tile space to a quadkey

import quadkey

qk = quadkey.from_geo((-105, 40), 17)
print qk.key # => 02310101232121212
qk = quadkey.from_geo((40, -105), 17)
print(qk.key) # => 02310101232121212
assert qk.level is 17
tile = qk.to_tile() # => [(x, y), z]

Expand All @@ -24,7 +24,7 @@ Not a lot of documentation here, but the implementation has quite a bit, so look
Install
-------

The package on pypi is quadtweet, so the recommended installation is with pip
The package on PyPI is quadkey, so the recommended installation is with pip

pip install quadkey

Expand All @@ -38,16 +38,19 @@ There are many straightforward methods, so I'll only go into detail of the uniqu
* is_ancestor()
* is_descendent()
* area()
* side()
* nearby()
* to_geo()
* to_tile()
* to_pixel()

####difference(to)
#### difference(to)

Gets the quadkeys between self and to forming a rectangle, inclusive.

qk.difference(to) -> [qk,,,,,to]

####unwind()
#### unwind()

Gets a list of all ancestors in descending order by level, inclusive.

Expand Down
60 changes: 35 additions & 25 deletions quadkey/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from util import precondition
from tile_system import TileSystem, valid_key
from .util import precondition
from .tile_system import TileSystem, valid_key, valid_geo

LAT_STR = 'lat'
LON_STR = 'lon'
Expand Down Expand Up @@ -27,7 +27,7 @@ def nearby(self):
perms = [(-1, -1), (-1, 0), (-1, 1), (0, -1),
(0, 1), (1, -1), (1, 0), (1, 1)]
tiles = set(
map(lambda perm: (abs(tile[0] + perm[0]), abs(tile[1] + perm[1])), perms))
[(abs(tile[0] + perm[0]), abs(tile[1] + perm[1])) for perm in perms])
return [TileSystem.tile_to_quadkey(tile, level) for tile in tiles]

def is_ancestor(self, node):
Expand All @@ -48,44 +48,50 @@ def is_descendent(self, node):
"""
return node.is_ancestor(self)

def side(self):
return 256 * TileSystem.ground_resolution(0, self.level)

def area(self):
size = TileSystem.map_size(self.level)
LAT = 0
res = TileSystem.ground_resolution(LAT, self.level)
side = (size / 2) * res
side = self.side()
return side * side

def xdifference(self, to):
@staticmethod
def xdifference(first, second):
""" Generator
Gives the difference of quadkeys between self and to
Generator in case done on a low level
Only works with quadkeys of same level
"""
x,y = 0,1
assert self.level == to.level
self_tile = list(self.to_tile()[0])
to_tile = list(to.to_tile()[0])
if self_tile[x] >= to_tile[x] and self_tile[y] <= self_tile[y]:
ne_tile, sw_tile = self_tile, to_tile
else:
sw_tile, ne_tile = self_tile, to_tile
cur = ne_tile[:]
while cur[x] >= sw_tile[x]:
while cur[y] <= sw_tile[y]:
yield from_tile(tuple(cur), self.level)
cur[y] += 1
x, y = 0, 1
assert first.level == second.level
self_tile = list(first.to_tile()[0])
to_tile = list(second.to_tile()[0])
se, sw, ne, nw = None, None, None, None
if self_tile[x] >= to_tile[x] and self_tile[y] <= to_tile[y]:
ne, sw = self_tile, to_tile
elif self_tile[x] <= to_tile[x] and self_tile[y] >= to_tile[y]:
sw, ne = self_tile, to_tile
elif self_tile[x] <= to_tile[x] and self_tile[y] <= to_tile[y]:
nw, se = self_tile, to_tile
elif self_tile[x] >= to_tile[x] and self_tile[y] >= to_tile[y]:
se, nw = self_tile, to_tile
cur = ne[:] if ne else se[:]
while cur[x] >= (sw[x] if sw else nw[x]):
while (sw and cur[y] <= sw[y]) or (nw and cur[y] >= nw[y]):
yield from_tile(tuple(cur), first.level)
cur[y] += 1 if sw else -1
cur[x] -= 1
cur[y] = ne_tile[y]
cur[y] = ne[y] if ne else se[y]

def difference(self, to):
""" Non generator version of xdifference
"""
return [qk for qk in self.xdifference(to)]
return [qk for qk in self.xdifference(self, to)]

def unwind(self):
""" Get a list of all ancestors in descending order of level, including a new instance of self
"""
return [ QuadKey(self.key[:l+1]) for l in reversed(range(len(self.key))) ]
return [ QuadKey(self.key[:l+1]) for l in reversed(list(range(len(self.key)))) ]

def to_tile(self):
return TileSystem.quadkey_to_tile(self.key)
Expand All @@ -98,11 +104,14 @@ def to_geo(self, centered=False):
return TileSystem.pixel_to_geo(pixel, lvl)

def __eq__(self, other):
return self.key == other.key
return isinstance(other, QuadKey) and self.key == other.key

def __ne__(self, other):
return not self.__eq__(other)

def __lt__(self, other):
return self.key.__lt__(other.key)

def __str__(self):
return self.key

Expand All @@ -112,6 +121,7 @@ def __repr__(self):
def __hash__(self):
return hash(self.key)

@precondition(lambda geo, level: valid_geo(*geo))
def from_geo(geo, level):
"""
Constucts a quadkey representation from geo and level
Expand Down
19 changes: 12 additions & 7 deletions quadkey/tile_system.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
from util import precondition
from .util import precondition
from math import sin, cos, atan, exp, log, pi


def valid_level(level):
LEVEL_RANGE = (1, 23)
LEVEL_RANGE = (1, 31)
return LEVEL_RANGE[0] <= level <= LEVEL_RANGE[1]


def valid_geo(lat, lon):
return TileSystem.LATITUDE_RANGE[0] <= lat <= TileSystem.LATITUDE_RANGE[1] \
and TileSystem.LONGITUDE_RANGE[0] <= lon <= TileSystem.LONGITUDE_RANGE[1] \


@precondition(lambda key: valid_level(len(key)))
def valid_key(key):
return TileSystem.KEY_PATTERN.match(key) is not None
Expand Down Expand Up @@ -84,7 +89,7 @@ def pixel_to_geo(pixel, level):
@staticmethod
def pixel_to_tile(pixel):
"""Transform pixel to tile coordinates"""
return pixel[0] / 256, pixel[1] / 256
return int(pixel[0] / 256), int(pixel[1] / 256)

@staticmethod
def tile_to_pixel(tile, centered=False):
Expand All @@ -99,10 +104,10 @@ def tile_to_pixel(tile, centered=False):
@precondition(lambda tile, lvl: valid_level(lvl))
def tile_to_quadkey(tile, level):
"""Transform tile coordinates to a quadkey"""
tile_x = tile[0]
tile_y = tile[1]
tile_x = int(tile[0])
tile_y = int(tile[1])
quadkey = ""
for i in xrange(level):
for i in range(level):
bit = level - i
digit = ord('0')
mask = 1 << (bit - 1) # if (bit - 1) > 0 else 1 >> (bit - 1)
Expand All @@ -118,7 +123,7 @@ def quadkey_to_tile(quadkey):
"""Transform quadkey to tile coordinates"""
tile_x, tile_y = (0, 0)
level = len(quadkey)
for i in xrange(level):
for i in range(level):
bit = level - i
mask = 1 << (bit - 1)
if quadkey[level - bit] == '1':
Expand Down
19 changes: 18 additions & 1 deletion tests/quadkey_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def testChildren(self):
qk = quadkey.from_str('0')
self.assertEqual(
[c.key for c in qk.children()], ['00', '01', '02', '03'])
qk = quadkey.from_str(''.join(['0' for x in xrange(23)]))
qk = quadkey.from_str(''.join(['0' for x in range(23)]))
self.assertEqual(qk.children(), [])

def testAncestry(self):
Expand Down Expand Up @@ -61,5 +61,22 @@ def testDifference(self):
self.assertEqual(diff, set([qk.key for qk in _to.difference(_from)]))
self.assertEqual(diff, set([qk.key for qk in _from.difference(_to)]))

def testDifference2(self):
qk1 = quadkey.QuadKey('033')
qk2 = quadkey.QuadKey('003')
diff = [qk.key for qk in qk1.difference(qk2)]
self.assertEqual(set(diff), set(['033', '031', '013', '032', '030', '012', '023', '021', '003']))

def testDifference3(self):
qk1 = quadkey.QuadKey('021')
qk2 = quadkey.QuadKey('011')
diff = [qk.key for qk in qk1.difference(qk2)]
self.assertEqual(set(diff), set(['011', '013', '031', '010', '012', '030', '001', '003', '021']))

def testSide(self):
qk = quadkey.QuadKey(''.join(['0'] * 10))
self.assertEqual(int(qk.side()), 39135)

def testArea(self):
qk = quadkey.QuadKey(''.join(['0'] * 10))
self.assertEqual(int(qk.area()), 1531607591)
2 changes: 1 addition & 1 deletion tests/tile_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def testMapScale(self):
geo = (40., -105.)
level = 7
dpi = 96
scale = 3540913.0290224836
scale = 3540913.029022482
self.assertEqual(scale, TileSystem.map_scale(geo[0], level, dpi))

def testGeoToPixel(self):
Expand Down