-
Notifications
You must be signed in to change notification settings - Fork 3
/
helpers.py
155 lines (127 loc) · 5.43 KB
/
helpers.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import math
import gdal
import numpy as np
from tornado.httpclient import AsyncHTTPClient
from skimage.draw import line
from tornado.gen import Return
from tornado import gen
ZOOM=12
class CoordSystem(object):
@classmethod
def lnglat_to_pixel(cls, lnglat, zoom=ZOOM):
lng, lat = lnglat
lat *= math.pi / 180.0
lng *= math.pi / 180.0
x = 128.0 / math.pi * 2**zoom * (lng + math.pi)
y = 128.0 / math.pi * 2**zoom * (math.pi - math.log(math.tan(math.pi / 4.0 + lat / 2.0)))
return round(x), round(y)
@classmethod
def lnglat_to_tile(cls, lnglat, zoom=ZOOM):
r = cls.lnglat_to_pixel(lnglat, zoom)
return cls.pixel_to_tile(r, zoom)
@classmethod
def pixel_to_tile(cls, pixel, zoom=ZOOM):
return (int(pixel[0]//256), 2**zoom - int(pixel[1]//256) - 1)
@classmethod
def pixel_to_lnglat(cls, point, zoom=ZOOM):
x, y = point
lat = (4.0 * math.atan(math.exp(math.pi - y * math.pi / (128.0 * 2**zoom))) - math.pi) / 2.0
lng = x * math.pi / (128.0 * 2**zoom) - math.pi
lat *= 180.0 / math.pi
lng *= 180.0 / math.pi
return lng, lat
def load_float32_image(buffer):
try:
gdal.FileFromMemBuffer('/vsimem/temp', buffer)
ds = gdal.Open('/vsimem/temp')
channel = ds.GetRasterBand(1).ReadAsArray()
ds = None #cleanup
gdal.Unlink('/vsimem/temp') #cleanup
return channel
except Exception, e:
ds = None #cleanup
gdal.Unlink('/vsimem/temp') #cleanup
raise e
class Tile(object):
def __init__(self, zoom, pixel, url_template):
self.zoom = zoom
self.url_template = url_template
self.pixel = (pixel[0]//256*256, pixel[1]//256*256) #round pixel to top left corner
self.data = self._retrieve_data() #data is actually a future
@property
def url(self):
tile = CoordSystem.pixel_to_tile(self.pixel, self.zoom)
return self.url_template.format(z=self.zoom, x=tile[0], y=tile[1])
@gen.coroutine
def _retrieve_data(self):
self.res = yield AsyncHTTPClient().fetch(self.url) #NOTE: currently will throw an http error if status code is not 200
raise Return(load_float32_image(self.res.body) if self.res.code == 200 else None)
class TileSampler(object):
"""Samples tile values. Everything is in global pixel space at specified zoom"""
def __init__(self, zoom=ZOOM, url_template='http://127.0.0.1:8080/{z}/{x}/{y}.tiff'):
self.zoom = zoom
self.url_template = url_template
self._tiles = {}
def _unique_rows(self, data):
"""Returns only the unique rows in a numpy array
Args:
data (array): numpy array to dedupe
Returns:
array: deduped numpy array
"""
uniq = np.unique(data.view(data.dtype.descr * data.shape[1]))
return uniq.view(data.dtype).reshape(-1, data.shape[1])
@gen.coroutine
def _sample_tile_pixels(self, tile_pixel, pixels):
"""Returns pixel's values which intersect a single tile"""
pixels = pixels - np.array(tile_pixel)
pixels = pixels[(pixels[:,0]>=0) & (pixels[:,0]<=255) & (pixels[:,1]>=0) & (pixels[:,1]<=255)]
xs = pixels[:,0]
ys = pixels[:,1]
tile_data = yield self.get_tile(tile_pixel).data
raise Return(tile_data[ys.astype(int), xs.astype(int)]) #numpy is row column
def get_tile(self, pixel):
"""Returns a tile. If tile already exists a cached version will be returned.
Args:
pixel ((int, int)): A pixel in the tile to be returned
"""
tile_pixel = (pixel[0]//256*256, pixel[1]//256*256)
if tile_pixel not in self._tiles:
self._tiles[tile_pixel] = Tile(self.zoom, tile_pixel, self.url_template)
return self._tiles[tile_pixel]
@gen.coroutine
def sample_pixels(self, pixels):
"""Samples arbitrary pixel values from the map. Returned order may not match input order (will for lines)
Args:
pixels (array): 2d numpy array where each row is a pixel to sample
Returns:
array: numpy array of values
"""
#determin required tiles
tile_pixels = np.floor_divide(pixels, 256)*256
tile_pixels = self._unique_rows(tile_pixels)
#preload tiles
for tile_pixel in tile_pixels: self.get_tile(tile_pixel)
#sample tiles
data = [(yield self._sample_tile_pixels(tile_pixel, pixels)) for tile_pixel in tile_pixels]
raise Return((np.concatenate(data), pixels))
@gen.coroutine
def sample_line(self, pixel1, pixel2):
"""Samples a line of pixel values in the map. Pixel values should be ordered from pixel1 to pixel2.
Args:
pixel1 ((int,int)): the 1st coordinate of the line in global pixel space
pixel2 ((int,int)): the 2nd coordinate of the line in global pixel space
Returns:
array: numpy array of values
"""
pixel1 = map(int, pixel1)
pixel2 = map(int, pixel2)
xs, ys = line(pixel1[0], pixel1[1], pixel2[0], pixel2[1])
pixels = np.dstack((xs, ys))[0]
raise Return((yield self.sample_pixels(pixels)))
@gen.coroutine
def sample_pixel(self, pixel):
raise Return((yield self.sample_pixels(np.array([pixel])))[0][0])
#Testing urls
#http://localhost:8888/elevation/-122.30933440000001/37.8666298
#http://localhost:8888/shed/-122.30933440000001/37.8666298/10/300