mirror of https://github.com/noerw/geo-dht
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
220 lines
7.6 KiB
Python
220 lines
7.6 KiB
Python
from direction import D
|
|
from geohash import Geohash
|
|
|
|
class ZCurve(object):
|
|
'''
|
|
Represents a position on a Z-Order Curve in 2D 0 - 1
|
|
https://en.wikipedia.org/wiki/Z-order_curve /
|
|
https://en.wikipedia.org/wiki/Moser%E2%80%93de_Bruijn_sequence 2 - 3
|
|
'''
|
|
|
|
EVENBITS = 0xaaaaaaaa # 0b10101010101010101010101010101010 (32 bit)
|
|
ODDBITS = 0x55555555 # 0b01010101010101010101010101010101 (32 bit)
|
|
|
|
z = 0
|
|
depth = 0
|
|
halfsplit = False
|
|
|
|
def __init__(self, z=0, depth=1, halfsplit=False):
|
|
'''
|
|
`z` is the position on the z-order curve.
|
|
|
|
`depth` is the recursion depth of the curve.
|
|
A ZCurve can have `4**depth` elements
|
|
|
|
`halfsplit` indicates that this instance covers double the area on the X
|
|
axis (itself + neighbouring cell), due to an uneven number of splits
|
|
|
|
TODO: should we implement the halfsplit like this? we might run into uglyness
|
|
when calculating neighbours, children...
|
|
It's basically an depth offset for the X axis, so treat it numerically?
|
|
|
|
TODO: implement halfsplit for all the operators:
|
|
- [x] __str__
|
|
- [x] __eq__
|
|
- [ ] __gt__, lt, ge, le
|
|
- [ ] __add__
|
|
- [ ] __contains__
|
|
- [ ] parent
|
|
- [ ] children
|
|
- [ ] neighbours
|
|
- [ ] region
|
|
'''
|
|
|
|
# shorthand to construct from bitstring
|
|
if type(z) == str:
|
|
_ = ZCurve.fromBitstring(z)
|
|
self.z = _.z
|
|
self.depth = _.depth
|
|
self.halfsplit = _.halfsplit
|
|
return
|
|
|
|
if z > 4 ** depth - 1:
|
|
raise ValueError('z-value %i does not exist on depth level %i' % (z, depth))
|
|
|
|
self.z = z
|
|
self.depth = depth
|
|
self.halfsplit = halfsplit
|
|
|
|
def fromXY(xy, depth):
|
|
'''
|
|
Constructs a ZCurve instance from a x,y pair, where x,y are indices to
|
|
the moser-debruijn sequence, so z = debruijn[x] + 2*debruijn[y]
|
|
'''
|
|
x, y = xy
|
|
if x >= 2 ** depth or y >= 2 ** depth:
|
|
raise ValueError('coordinate %s does not exist on depth level %i' % (xy, depth))
|
|
|
|
# interleave bits https://graphics.stanford.edu/~seander/bithacks.html#InterleaveTableObvious
|
|
z = 0
|
|
for i in range(32):
|
|
z |= (x & 1 << i) << i | (y & 1 << i) << (i + 1)
|
|
|
|
return ZCurve(z, depth)
|
|
|
|
def fromBitstring(bitstring):
|
|
# uneven number of bits means we have a halfsplit:
|
|
# set last split on X to 0, but we treat it as covering both 0 and 1
|
|
halfsplit = len(bitstring) % 2 != 0
|
|
if halfsplit:
|
|
bitstring += '0'
|
|
|
|
depth = int(len(bitstring) / 2)
|
|
z = int(bitstring, base=2)
|
|
return ZCurve(z, depth, halfsplit)
|
|
|
|
def fromLatLon(lat, lon, depth, halfsplit=False):
|
|
z = Geohash.encodePoint(lat, lon, depth * 2, Geohash.NUMERIC_MSB)
|
|
return ZCurve(z, depth, halfsplit)
|
|
|
|
def xy(self):
|
|
''' returns indices to the debruijn sequence
|
|
'''
|
|
x = 0
|
|
y = 0
|
|
# de-interleave bits
|
|
for i in range(32):
|
|
if i % 2 == 0:
|
|
x |= (self.z & 1 << i) >> int(i / 2)
|
|
else:
|
|
y |= (self.z & 1 << i) >> int(i / 2 + 1)
|
|
return x, y
|
|
|
|
def debruijn(self):
|
|
''' given indices to the moser-debuijn sequence, returns debruijn values
|
|
'''
|
|
xDebruijn = self.z & self.ODDBITS
|
|
yDebruijn = self.z & self.EVENBITS
|
|
return xDebruijn, yDebruijn
|
|
|
|
def region(self, minXy=None, maxXy=None):
|
|
'''
|
|
returns the covered region as `((minX, minY), (maxX, maxY))` tuple
|
|
scaled to the `minXy` and `maxXy` range
|
|
'''
|
|
minXy = minXy or (-180, -90)
|
|
maxXy = maxXy or (180, 90)
|
|
|
|
if self.depth == 0:
|
|
return minXy, maxXy
|
|
|
|
x, y = self.xy()
|
|
numY = 2 ** self.depth
|
|
numX = numY / 2 if self.halfsplit else numY
|
|
|
|
rangeX = maxXy[0] - minXy[0]
|
|
rangeY = maxXy[1] - minXy[1]
|
|
minX = minXy[0] + x * rangeX / numX
|
|
minY = minXy[1] + y * rangeY / numY
|
|
maxX = minX + rangeX / numX
|
|
maxY = minY + rangeY / numY
|
|
|
|
return (minX, minY), (maxX, maxY)
|
|
|
|
def neighbours(self):
|
|
# https://en.wikipedia.org/wiki/Z-order_curve#Coordinate_values adapted to 32bit
|
|
return {
|
|
D.NORTH: ZCurve((((self.z & self.EVENBITS) - 1 & self.EVENBITS) | (self.z & self.ODDBITS)) % 4 ** self.depth, self.depth),
|
|
D.SOUTH: ZCurve((((self.z | self.ODDBITS) + 1 & self.EVENBITS) | (self.z & self.ODDBITS)) % 4 ** self.depth, self.depth),
|
|
D.WEST: ZCurve((((self.z & self.ODDBITS) - 1 & self.ODDBITS) | (self.z & self.EVENBITS)) % 4 ** self.depth, self.depth),
|
|
D.EAST: ZCurve((((self.z | self.EVENBITS) + 1 & self.ODDBITS) | (self.z & self.EVENBITS)) % 4 ** self.depth, self.depth),
|
|
}
|
|
|
|
def parent(self, depthOffset=1):
|
|
''' returns the corresponding cell in the z-order curve of one less recursion
|
|
'''
|
|
z = self.z >> (2 * depthOffset)
|
|
depth = max(0, self.depth - depthOffset)
|
|
return ZCurve(z, depth)
|
|
|
|
def children(self, depthOffset=1):
|
|
''' returns the corresponding cells in the z-order curve of deeper recurson
|
|
'''
|
|
zBase = self.z << (2 * depthOffset)
|
|
numChildren = 4 ** depthOffset
|
|
return [ZCurve(z, self.depth + depthOffset) for z in range(zBase, zBase + numChildren)]
|
|
|
|
def __str__(self):
|
|
# encode as bitstring
|
|
res = ''
|
|
skipLsb = 1 if self.halfsplit else 0 # don't encode the last X-axis split
|
|
for i in range(skipLsb, self.depth * 2):
|
|
bit = (self.z & (1 << i)) >> i
|
|
res += str(bit)
|
|
return res[::-1] # reverse, MSB first
|
|
|
|
def __add__(self, other):
|
|
self.ensureSameType(other)
|
|
|
|
if other.depth != self.depth:
|
|
deeper = other if self.depth < other.depth else self
|
|
higher = self if self.depth < other.depth else other
|
|
return deeper + higher.children(deeper.depth - higher.depth)[0]
|
|
|
|
# https://en.wikipedia.org/wiki/Z-order_curve#Coordinate_values adapted to 32bit
|
|
z = (
|
|
((self.z | self.EVENBITS) + (other.z & self.ODDBITS) & self.ODDBITS) |
|
|
((self.z | self.ODDBITS) + (other.z & self.EVENBITS) & self.EVENBITS)
|
|
) % 4 ** self.depth
|
|
return ZCurve(z, self.depth)
|
|
|
|
def __contains__(self, other):
|
|
self.ensureSameType(other)
|
|
if self.depth > other.depth:
|
|
return False
|
|
elif self.depth < other.depth:
|
|
# we're larger, but we need to check overlap
|
|
# TODO: self.halfsplit
|
|
return self == other.parent(other.depth - self.depth)
|
|
else:
|
|
return self == other
|
|
|
|
def __lt__(self, other):
|
|
# compares the "area" that this instance covers
|
|
self.ensureSameType(other)
|
|
return self.depth > other.depth
|
|
|
|
def __gt__(self, other):
|
|
self.ensureSameType(other)
|
|
return self.depth < other.depth
|
|
|
|
def __le__(self, other):
|
|
self.ensureSameType(other)
|
|
return self.depth <= other.depth
|
|
|
|
def __ge__(self, other):
|
|
self.ensureSameType(other)
|
|
return self.depth >= other.depth
|
|
|
|
def __eq__(self, other):
|
|
self.ensureSameType(other)
|
|
return (
|
|
self.z == other.z and
|
|
self.depth == other.depth and
|
|
self.halfsplit == other.halfsplit
|
|
)
|
|
|
|
def ensureSameType(self, other):
|
|
if type(other) != ZCurve:
|
|
raise TypeError('cant compare ZCurve with %s', type(other))
|