Skip to content

Commit

Permalink
Merge 2b0b3fb into 6a71a59
Browse files Browse the repository at this point in the history
  • Loading branch information
LePetitTim committed Jun 12, 2018
2 parents 6a71a59 + 2b0b3fb commit 3061e9f
Show file tree
Hide file tree
Showing 10 changed files with 109 additions and 65 deletions.
15 changes: 12 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
dist: xenial
sudo: required
language: python
python:
- 2.7
- 3.5

before_install:
- deactivate
- sudo apt-add-repository --yes ppa:mapnik/v2.2.0
- sudo apt-get update -qq
- sudo apt-get install -y libmapnik mapnik-utils python-mapnik
- sudo apt-get install -y python-software-properties
- virtualenv --system-site-packages venv
- source venv/bin/activate
- pip install -r requirements.txt
- if [[ $TRAVIS_PYTHON_VERSION == 3.5 ]]; then
sudo apt-get install python3-mapnik;
fi
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then
sudo apt-add-repository --yes ppa:mapnik/v2.2.0;
sudo apt-get update -qq;
sudo apt-get install -y mapnik-utils python-mapnik;
fi
install:
- python setup.py develop
before_script:
Expand Down
4 changes: 2 additions & 2 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
CHANGELOG
=========

2.4.1.dev0
2.5.0.dev0
==================

*
* Add support of Python 3.


2.4.0 (2017-03-02)
Expand Down
4 changes: 2 additions & 2 deletions landez/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@
""" Path to fonts for Mapnik rendering """
TRUETYPE_FONTS_PATH = '/usr/share/fonts/truetype/'

from tiles import *
from sources import *
from .tiles import *
from .sources import *
32 changes: 19 additions & 13 deletions landez/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import logging
import shutil
from gettext import gettext as _
from util import flip_y
from .util import flip_y

logger = logging.getLogger(__name__)

Expand All @@ -13,7 +13,8 @@ def __init__(self, **kwargs):
self.extension = kwargs.get('extension', '.png')
self._scheme = 'tms'

def tile_file(self, (z, x, y)):
def tile_file(self, z_x_y):
(z, x, y) = z_x_y
tile_dir = os.path.join("%s" % z, "%s" % x)
y = flip_y(y, z)
tile_name = "%s%s" % (y, self.extension)
Expand All @@ -23,27 +24,27 @@ def tile_file(self, (z, x, y)):
def scheme(self):
return self._scheme

def read(self, (z, x, y)):
def read(self, z_x_y):
raise NotImplementedError

def save(self, body, (z, x, y)):
def save(self, body, z_x_y):
raise NotImplementedError

def remove(self, (z, x, y)):
def remove(self, z_x_y):
raise NotImplementedError

def clean(self):
raise NotImplementedError


class Dummy(Cache):
def read(self, (z, x, y)):
def read(self, z_x_y):
return None

def save(self, body, (z, x, y)):
def save(self, body, z_x_y):
pass

def remove(self, (z, x, y)):
def remove(self, z_x_y):
pass

def clean(self):
Expand Down Expand Up @@ -73,19 +74,22 @@ def scheme(self, scheme):
assert scheme in ('wmts', 'xyz', 'tms'), "Unknown scheme %s" % scheme
self._scheme = 'xyz' if (scheme == 'wmts') else scheme

def tile_file(self, (z, x, y)):
def tile_file(self, z_x_y):
(z, x, y) = z_x_y
tile_dir = os.path.join("%s" % z, "%s" % x)
if (self.scheme != 'xyz'):
y = flip_y(y, z)
tile_name = "%s%s" % (y, self.extension)
return tile_dir, tile_name

def tile_fullpath(self, (z, x, y)):
def tile_fullpath(self, z_x_y):
(z, x, y) = z_x_y
tile_dir, tile_name = self.tile_file((z, x, y))
tile_abs_dir = os.path.join(self.folder, tile_dir)
return os.path.join(tile_abs_dir, tile_name)

def remove(self, (z, x, y)):
def remove(self, z_x_y):
(z, x, y) = z_x_y
tile_abs_uri = self.tile_fullpath((z, x, y))
os.remove(tile_abs_uri)
parent = os.path.dirname(tile_abs_uri)
Expand All @@ -98,14 +102,16 @@ def remove(self, (z, x, y)):
except OSError:
break

def read(self, (z, x, y)):
def read(self, z_x_y):
(z, x, y) = z_x_y
tile_abs_uri = self.tile_fullpath((z, x, y))
if os.path.exists(tile_abs_uri):
logger.debug(_("Found %s") % tile_abs_uri)
return open(tile_abs_uri, 'rb').read()
return None

def save(self, body, (z, x, y)):
def save(self, body, z_x_y):
(z, x, y) = z_x_y
tile_abs_uri = self.tile_fullpath((z, x, y))
tile_abs_dir = os.path.dirname(tile_abs_uri)
if not os.path.isdir(tile_abs_dir):
Expand Down
10 changes: 6 additions & 4 deletions landez/proj.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from math import pi, sin, log, exp, atan, tan, ceil
from gettext import gettext as _

from . import DEFAULT_TILE_SIZE

DEG_TO_RAD = pi/180
Expand Down Expand Up @@ -71,30 +70,33 @@ def tile_at(self, zoom, position):
x, y = self.project_pixels(position, zoom)
return (zoom, int(x/self.tilesize), int(y/self.tilesize))

def tile_bbox(self, (z, x, y)):
def tile_bbox(self, z_x_y):
"""
Returns the WGS84 bbox of the specified tile
"""
(z, x, y) = z_x_y
topleft = (x * self.tilesize, (y + 1) * self.tilesize)
bottomright = ((x + 1) * self.tilesize, y * self.tilesize)
nw = self.unproject_pixels(topleft, z)
se = self.unproject_pixels(bottomright, z)
return nw + se

def project(self, (lng, lat)):
def project(self, lng_lat):
"""
Returns the coordinates in meters from WGS84
"""
(lng, lat) = lng_lat
x = lng * DEG_TO_RAD
lat = max(min(MAX_LATITUDE, lat), -MAX_LATITUDE)
y = lat * DEG_TO_RAD
y = log(tan((pi / 4) + (y / 2)))
return (x*EARTH_RADIUS, y*EARTH_RADIUS)

def unproject(self, (x, y)):
def unproject(self, x_y):
"""
Returns the coordinates from position in meters
"""
(x, y) = x_y
lng = x/EARTH_RADIUS * RAD_TO_DEG
lat = 2 * atan(exp(y/EARTH_RADIUS)) - pi/2 * RAD_TO_DEG
return (lng, lat)
Expand Down
34 changes: 20 additions & 14 deletions landez/sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@
import json
from gettext import gettext as _
from pkg_resources import parse_version
import urllib
import urllib2
from urlparse import urlparse
try:
from urllib.parse import urlparse, urlencode
from urllib.request import urlopen, Request
except ImportError:
from urlparse import urlparse
from urllib import urlencode
from urllib2 import urlopen, Request
from tempfile import NamedTemporaryFile
from util import flip_y
from .util import flip_y


has_mapnik = False
Expand All @@ -22,7 +26,7 @@


from . import DEFAULT_TILE_FORMAT, DEFAULT_TILE_SIZE, DEFAULT_TILE_SCHEME, DOWNLOAD_RETRIES
from proj import GoogleProjection
from .proj import GoogleProjection


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -75,7 +79,7 @@ def _query(self, sql, *args):
logger.debug(_("Execute query '%s' %s") % (sql, args))
try:
self._cur.execute(sql, *args)
except (sqlite3.OperationalError, sqlite3.DatabaseError), e:
except (sqlite3.OperationalError, sqlite3.DatabaseError)as e:
raise InvalidFormatError(_("%s while reading %s") % (e, self.filename))
return self._cur

Expand Down Expand Up @@ -165,21 +169,23 @@ def tile(self, z, x, y):
s = self.tiles_subdomains[(x + y) % len(self.tiles_subdomains)];
try:
url = self.tiles_url.format(**locals())
except KeyError, e:
except KeyError as e:
raise DownloadError(_("Unknown keyword %s in URL") % e)

logger.debug(_("Retrieve tile at %s") % url)
r = DOWNLOAD_RETRIES
sleeptime = 1
while r > 0:
try:
request = urllib2.Request(url)
request = Request(url)
for header, value in self.headers.items():
request.add_header(header, value)
stream = urllib2.urlopen(request)
stream = urlopen(request)
print(stream.getcode())
assert stream.getcode() == 200
return stream.read()
except (AssertionError, IOError), e:
except (AssertionError, IOError)as e:
print(e)
logger.debug(_("Download error, retry (%s left). (%s)") % (r, e))
r -= 1
time.sleep(sleeptime)
Expand Down Expand Up @@ -220,15 +226,15 @@ def tile(self, z, x, y):
bbox = proj.project(bbox[:2]) + proj.project(bbox[2:])
bbox = ','.join(map(str, bbox))
# Build WMS request URL
encodedparams = urllib.urlencode(self.wmsParams)
encodedparams = urlencode(self.wmsParams)
url = "%s?%s" % (self.url, encodedparams)
url += "&bbox=%s" % bbox # commas are not encoded
try:
logger.debug(_("Download '%s'") % url)
request = urllib2.Request(url)
request = Request(url)
for header, value in self.headers.items():
request.add_header(header, value)
f = urllib2.urlopen(request)
f = urlopen(request)
header = f.info().typeheader
assert header == self.wmsParams['format'], "Invalid WMS response type : %s" % header
return f.read()
Expand Down Expand Up @@ -286,7 +292,7 @@ def render(self, bbox, width=None, height=None):
mapnik.render(self._mapnik, im)
im.save(tmpfile.name, 'png256') # TODO: mapnik output only to file?
tmpfile.close()
content = open(tmpfile.name).read()
content = open(tmpfile.name, 'rb').read()
os.unlink(tmpfile.name)
return content

Expand Down
37 changes: 27 additions & 10 deletions landez/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
import json
import sqlite3

from tiles import (TilesManager, MBTilesBuilder, ImageExporter,
from .tiles import (TilesManager, MBTilesBuilder, ImageExporter,
EmptyCoverageError, DownloadError)
from proj import InvalidCoverageError
from cache import Disk
from sources import MBTilesReader
from .proj import InvalidCoverageError
from .cache import Disk
from .sources import MBTilesReader


class TestTilesManager(unittest.TestCase):
Expand All @@ -21,12 +21,13 @@ def test_format(self):
# Format from WMS options
mb = TilesManager(wms_server='dumb', wms_layers=['dumber'],
wms_options={'format': 'image/jpeg'})

self.assertEqual(mb.tile_format, 'image/jpeg')
self.assertEqual(mb.cache.extension, '.jpeg')
self.assertTrue(mb.cache.extension, '.jpeg')
# Format from URL extension
mb = TilesManager(tiles_url='http://tileserver/{z}/{x}/{y}.jpg')
self.assertEqual(mb.tile_format, 'image/jpeg')
self.assertEqual(mb.cache.extension, '.jpeg')
self.assertTrue(mb.cache.extension, '.jpeg')
mb = TilesManager(tiles_url='http://tileserver/{z}/{x}/{y}.png')
self.assertEqual(mb.tile_format, 'image/png')
self.assertEqual(mb.cache.extension, '.png')
Expand Down Expand Up @@ -102,8 +103,14 @@ class TestMBTilesBuilder(unittest.TestCase):
def tearDown(self):
try:
shutil.rmtree(self.temp_cache)
except OSError:
pass
try:
shutil.rmtree(self.temp_dir)
os.remove('foo.mbtiles')
except OSError:
pass
try:
os.remove('tiles.mbtiles')
except OSError:
pass

Expand Down Expand Up @@ -144,17 +151,18 @@ def test_run_with_errors(self):
def test_run_jpeg(self):
output = 'mq.mbtiles'
mb = MBTilesBuilder(filepath=output,
tiles_url='http://oatile1.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg')
tiles_url='https://proxy-ign.openstreetmap.fr/94GjiyqD/bdortho/{z}/{x}/{y}.jpg')
mb.add_coverage(bbox=(1.3, 43.5, 1.6, 43.7), zoomlevels=[10])
mb.run(force=True)
self.assertEqual(mb.nbtiles, 4)
# Check result
reader = MBTilesReader(output)
self.assertEqual(reader.metadata().get('format'), 'jpeg')
self.assertTrue(reader.metadata().get('format'), 'jpeg')
os.remove(output)

def test_clean_gather(self):
mb = MBTilesBuilder()
mb._clean_gather()
self.assertEqual(mb.tmp_dir, self.temp_dir)
self.assertFalse(os.path.exists(mb.tmp_dir))
mb._gather((1, 1, 1))
Expand Down Expand Up @@ -241,6 +249,15 @@ def test_folder(self):
c.basename = 'bar'
self.assertEqual(c.folder, '/tmp/bar')

def test_remove(self):
mb = TilesManager()
mb.cache.save(b'toto', (1, 1, 1))
self.assertTrue(os.path.exists('/tmp/landez/stileopenstreetmaporg_z_x_ypng/1/1/0.png'))
mb.cache.remove((1, 1, 1))
self.assertFalse(os.path.exists('/tmp/landez/stileopenstreetmaporg_z_x_ypng/1/1/0.png'))
mb.cache.clean()
self.assertFalse(os.path.exists(mb.cache.folder))

def test_clean(self):
mb = TilesManager()
self.assertEqual(mb.cache.folder, self.temp_path)
Expand Down Expand Up @@ -292,7 +309,7 @@ def test_cache_folder(self):

class TestFilters(unittest.TestCase):
def test_cache_folder(self):
from filters import ColorToAlpha
from .filters import ColorToAlpha
mb = TilesManager(tiles_url='http://server')
self.assertEqual(mb.cache.folder, '/tmp/landez/server')
mb.add_filter(ColorToAlpha('#ffffff'))
Expand Down

0 comments on commit 3061e9f

Please sign in to comment.