Adding classes Address and Coord, moving functions into the classes.
[treecutter.git] / xinclude / address.py
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 import time
5 from os import path
6 from httplib2 import Http
7 import urllib
8 from math import *
9 from lxml import etree
10 from PIL import Image, ImageDraw
11 from StringIO import StringIO
12 from geohash import encode
13 from geographiclib.geodesic import Geodesic
14
15 # EC Equator lenght
16 EC = 40075016.686 # meter
17 # ER Earth radius
18 ER = 6372798.2 # meter
19 # Availible zoom levels in OSM
20 ZOOMRANGE = range(1, 18)
21 # tile size
22 TS = 256
23
24
25 h = Http(".cache")
26
27 class Coord(object):
28     def __init__(self, lat, lon):
29         self.latitude  = float(lat)
30         self.longitude = float(lon)
31         self.image = None
32
33     def osmlink(self):
34         return "http://www.openstreetmap.org/?mlat=%s&mlon=%s&zoom=18&layers=M"\
35             % (self.latitude,self.longitude)
36
37     def dms(self):
38         ns = self.latitude
39         ew = self.longitude
40         mnt,sec = divmod(ns*3600,60)
41         deg,mnt = divmod(mnt,60)
42         out = u'''%d°%2d'%5.2f"%s''' % ( deg,mnt,sec,'N')
43         mnt,sec = divmod(ew*3600,60)
44         deg,mnt = divmod(mnt,60)
45         out +=  u''' %d°%2d'%05.2f"%s''' % ( deg,mnt,sec,'E')
46         return out
47
48     def lontile(lon, zoom):
49         tile = ((lon + 180) / 360) * (2**zoom)
50         return tile
51
52     def lattile(lat, zoom):
53         tile = (1 - log(tan(lat * pi/180) + 1 / cos(lat* pi/180)) / pi) /2 * 2**zoom
54     #    tile = (1-log(tan(radians(lat))+1/cos(radians(lat)))/pi)/2*2**zoom
55         return tile
56
57     def coordtile(coord, zoom):
58         x = lontile(coord[1],zoom)
59         y = lattile(coord[0],zoom)
60         return (y,x)
61
62     def tile(x,y,z):
63         return 'http://tile.openstreetmap.org/%s/%s/%s.png' % (z,x,y)
64
65     def link(coord,zoom):
66         (x, y) = coordtile(coord,zoom)
67         x = int(floor(x))
68         y = int(floor(y))
69         return tile(x,y,zoom)
70
71     def offset(coord, zoom):
72         (x, y) = coordtile(coord,zoom)
73         xo = int(floor((x-floor(x))*TS))
74         yo = int(floor((y-floor(y))*TS))
75         return (xo, yo)
76
77     def png(self,zoom=15,size=(TS,TS)):
78         filename = encode(self.latitude, self.longitude)+'.png'
79         if path.isfile(filename):
80             if path.getctime(filename) > time.time() - 60*60*24*2:
81                 return
82         im = Image.new("RGB", size, None)
83         center = (size[0]/2, size[1]/2)
84         nst = ceil(center[0]-TS/2)/TS
85         ewt = ceil(center[1]-TS/2)/TS
86         x, y = offset(co,zoom)
87         (ns, ew) = coordtile(co,zoom)
88         if x < TS/2:
89             e = 1
90             w = 0
91         if x > TS/2:
92             e = 0
93             w = 1
94         if y < TS/2:
95             n = 1
96             s = 0
97         if y > TS/2:
98             n = 0
99             s = 1
100         ul = (ns-nst-n, ew-ewt-e)
101         lr = (ns+nst+s, ew+ewt+w)
102         lattiles = range(int(ul[0]),int(lr[0])+1)
103         lontiles =  range(int(ul[1]),int(lr[1])+1)
104 #        url = link(c,zoom)
105         size = (len(lontiles)*TS,len(lattiles)*TS)
106         grid = Image.new("RGB", size, None)
107         for yi, y in enumerate(lattiles):
108             for xi, x in enumerate(lontiles):
109                 url = tile(x,y,zoom)
110                 time.sleep(1)
111                 request, content = h.request(url)
112                 img = Image.open(StringIO(content))
113                 box = (xi*TS, yi*TS)
114                 grid.paste(img, box)
115         t = coordtile(co,zoom)
116         o = offset(co,zoom)
117         yp = [i for i,x in enumerate(lattiles) if x == int(t[0])][0]*TS+o[1]
118         xp = [i for i,x in enumerate(lontiles) if x == int(t[1])][0]*TS+o[0]
119         mark(grid, (xp,yp))
120         gridc = grid.crop((xp-TS/2,yp-TS/2,xp+TS/2,yp+TS/2))
121         gridc.save(filename)
122
123
124     def db_xml(self):
125         uri = etree.Element(DB+'uri',nsmap=NSMAP)
126         ln  = etree.SubElement(uri,  DB+'link')
127         ln.set(XLINK+'href',self.osmlink())
128         imo  = etree.SubElement(ln,  DB+'inlinemediaobject')
129         io   = etree.SubElement(imo, DB+'imageobject', condition="web")
130         idat = etree.SubElement(io , DB+'imagedata',
131                                 fileref=encode(self.latitude, self.longitude)+'.png',
132                                 format='PNG')
133         to   = etree.SubElement(imo, DB+'textobject')
134         ph   = etree.SubElement(to,  DB+'phrase')
135         ph.text = "geo:"+str(self.latitude)+","+str(self.longitude)
136         para = etree.SubElement(ln,  DB+'para')
137         para.text = self.dms()
138         return uri
139
140 class Address(object):
141     """Address object to contain everything known about an address"""
142     def __init__(self,address):
143         self._address_string = address
144         self._root = etree.Element(DB+'address',nsmap=NSMAP)
145         self._coord = None
146
147     def geocode(self,country=None):
148         params = { 'q': self._address_string,
149                    'addressdetails': 1,
150                    'limit': 1,
151                    'format': 'xml',
152                    'polygon': 0 }
153
154         if country:
155             params['countrycodes'] = country
156
157         base_url = 'http://nominatim.openstreetmap.org/search?%s'
158         url = base_url % urllib.urlencode(params)
159         resp, content = h.request(url)
160         root = etree.fromstring(content)
161         place = root.find("place")
162         if place is not None:
163             print (etree.tostring(root, pretty_print=True))
164             self._coord=Coord(place.get("lat"),place.get("lon"))
165             return 1
166         else:
167             print resp
168             print content
169             return 0
170
171     def db_xml(self):
172         return self._coord.db_xml()
173
174
175 def distance(p1, p2):
176     res = Geodesic.WGS84.Inverse(p1[0], p1[1], p2[0], p2[1])
177     return res['s12']
178
179 def mark(image, coord):
180     draw = ImageDraw.Draw(image)
181     x, y = coord
182     r = 5
183     bbox = (x-r, y-r, x+r, y+r)
184     draw.ellipse(bbox, outline="red")
185
186 def mapimages(coords, zoom=15,size=(TS,TS)):
187         minlat = 1000
188         maxlat = 0
189         minlon = 1000
190         maxlon = 0
191         for c in coords:
192             minlat = min(minlat,c[0])
193             maxlat = max(maxlat,c[0])
194             minlon = min(minlon,c[1])
195             maxlon = max(maxlon,c[1])
196         # Find minimal bounding box and expand it 5%
197         hyp = distance((maxlat,minlon),(minlat,maxlon))
198         hyp = hyp*0.05
199         uld =  Geodesic.WGS84.Direct(maxlat, minlon, 315, hyp)
200         urd =  Geodesic.WGS84.Direct(maxlat, maxlon,  45, hyp)
201         lld =  Geodesic.WGS84.Direct(minlat, minlon, 225, hyp)
202         lrd =  Geodesic.WGS84.Direct(minlat, maxlon, 135, hyp)
203
204         ul = (uld['lat2'],uld['lon2'])
205         ur = (urd['lat2'],urd['lon2'])
206         ll = (lld['lat2'],lld['lon2'])
207         lr = (lrd['lat2'],lrd['lon2'])
208         top = distance(ul,ur)
209         bottom = distance(ll,lr)
210         left = distance(ul,ll)
211         right = distance(ur,lr)
212 #    m_per_pix = EC*abs(cos(lat))/2**(zoomlevel+8)
213 #    m_per_pix = ER*2*pi*abs(cos(lat))/2**(zoomlevel+8)
214         for zoom in range(1,18):
215             t = 2**(zoom)*TS/(ER*2*pi*abs(cos(ul[0])))
216             toppix = t*top
217             leftpix = t*left
218             b = 2**(zoom)*TS/(ER*2*pi*abs(cos(ll[0])))
219             bottompix = b*bottom
220             rightpix  = b*right
221 #            print "Zoom: %s : %s %s %s %s" % (zoom, toppix, bottompix, leftpix, rightpix)
222             if max(toppix,bottompix,leftpix,rightpix) > TS*4:
223                 break
224 #        print "Zoom to use : %s" % (zoom)
225         ult = coordtile(ul,zoom)
226         lrt = coordtile(lr,zoom)
227         lattiles = range(int(ult[0]),int(lrt[0])+1)
228         lontiles =  range(int(ult[1]),int(lrt[1])+1)
229         size = (len(lontiles)*TS,len(lattiles)*TS)
230         grid = Image.new("RGB", size, None)
231         img = []
232         for yi, y in enumerate(lattiles):
233             for xi, x in enumerate(lontiles):
234                 url = tile(x,y,zoom)
235 #                print url
236                 time.sleep(1)
237                 request, content = h.request(url)
238                 img = Image.open(StringIO(content))
239                 box = (xi*TS, yi*TS)
240                 grid.paste(img, box)
241         for c in coords:
242             t = coordtile(c,zoom)
243             o = offset(c,zoom)
244             yp = [i for i,x in enumerate(lattiles) if x == int(t[0])][0]*TS+o[1]
245             xp = [i for i,x in enumerate(lontiles) if x == int(t[1])][0]*TS+o[0]
246             mark(grid, (xp,yp))
247
248         t = coordtile(ul,zoom)
249         o = offset(ul,zoom)
250         yp = [i for i,x in enumerate(lattiles) if x == int(t[0])][0]*TS+o[1]
251         xp = [i for i,x in enumerate(lontiles) if x == int(t[1])][0]*TS+o[0]
252         t = coordtile(lr,zoom)
253         o = offset(lr,zoom)
254         ypl = [i for i,x in enumerate(lattiles) if x == int(t[0])][0]*TS+o[1]
255         xpl = [i for i,x in enumerate(lontiles) if x == int(t[1])][0]*TS+o[0]
256         gridc = grid.crop((xp,yp,xpl,ypl))
257         gridc.show()
258 #        gridc.save("cap-un.png")