Skip to content

Commit

Permalink
[Closes #3982] Improve Thumbnail method so that it can include a back…
Browse files Browse the repository at this point in the history
…ground also
  • Loading branch information
afabiani committed Oct 12, 2018
1 parent 88fac78 commit f43190c
Show file tree
Hide file tree
Showing 11 changed files with 297 additions and 96 deletions.
Binary file removed celerybeat-schedule
Binary file not shown.
33 changes: 19 additions & 14 deletions geonode/base/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,7 @@ def deg_len():
self.bbox_x1 = lon + distance_x_degrees
self.bbox_y0 = lat - distance_y_degrees
self.bbox_y1 = lat + distance_y_degrees
self.srid = 'EPSG:4326'

def set_bounds_from_bbox(self, bbox, srid):
"""
Expand All @@ -993,24 +994,28 @@ def set_bounds_from_bbox(self, bbox, srid):
self.bbox_y1 = bbox[3]
self.srid = srid

minx, maxx, miny, maxy = [float(c) for c in bbox]
x = (minx + maxx) / 2
y = (miny + maxy) / 2
(center_x, center_y) = forward_mercator((x, y))
if srid == "EPSG:4326":
minx, maxx, miny, maxy = [float(c) for c in bbox]
x = (minx + maxx) / 2
y = (miny + maxy) / 2
(center_x, center_y) = forward_mercator((x, y))

xdiff = maxx - minx
ydiff = maxy - miny
xdiff = maxx - minx
ydiff = maxy - miny

zoom = 0
zoom = 0

if xdiff > 0 and ydiff > 0:
width_zoom = math.log(360 / xdiff, 2)
height_zoom = math.log(360 / ydiff, 2)
zoom = math.ceil(min(width_zoom, height_zoom))
if xdiff > 0 and ydiff > 0:
width_zoom = math.log(360 / xdiff, 2)
height_zoom = math.log(360 / ydiff, 2)
zoom = math.ceil(min(width_zoom, height_zoom))

self.zoom = zoom
self.center_x = center_x
self.center_y = center_y
try:
self.zoom = zoom
self.center_x = center_x
self.center_y = center_y
except BaseException:
pass

def download_links(self):
"""assemble download links for pycsw"""
Expand Down
120 changes: 118 additions & 2 deletions geonode/geoserver/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1754,11 +1754,11 @@ def get_time_info(layer):
"esriFieldTypeXML": "xsd:anyType"}


def _render_thumbnail(req_body):
def _render_thumbnail(req_body, width=240, height=180):
spec = _fixup_ows_url(req_body)
url = "%srest/printng/render.png" % ogc_server_settings.LOCATION
hostname = urlparse(settings.SITEURL).hostname
params = dict(width=240, height=180, auth="%s,%s,%s" % (hostname, _user, _password))
params = dict(width=width, height=height, auth="%s,%s,%s" % (hostname, _user, _password))
url = url + "?" + urllib.urlencode(params)

# @todo annoying but not critical
Expand All @@ -1783,6 +1783,122 @@ def _render_thumbnail(req_body):
return content


def _prepare_thumbanil_body_from_opts(request_body):
import mercantile
from geonode.utils import (_v,
bbox_to_projection,
bounds_to_zoom_level)
# Defaults
_img_src_template = """<img src='{ogc_location}'
style='width: {width}px; height: {height}px;
left: {left}px; top: {top}px;
opacity: 1; visibility: inherit; position: absolute;'/>\n"""

def decimal_encode(bbox):
import decimal
_bbox = []
for o in [float(coord) for coord in bbox]:
if isinstance(o, decimal.Decimal):
o = (str(o) for o in [o])
_bbox.append(o)
# Must be in the form : [x0, x1, y0, y1
return [_bbox[0], _bbox[2], _bbox[1], _bbox[3]]

# Sanity Checks
if 'bbox' not in request_body:
return None
if 'srid' not in request_body:
return None
for coord in request_body['bbox']:
if not coord:
return None

width = 240
if 'width' in request_body:
width = request_body['width']
height = 200
if 'height' in request_body:
height = request_body['height']
smurl = None
if 'smurl' in request_body:
smurl = request_body['smurl']
if not smurl and getattr(settings, 'THUMBNAIL_GENERATOR_DEFAULT_BG', None):
smurl = settings.THUMBNAIL_GENERATOR_DEFAULT_BG

layers = None
thumbnail_create_url = None
if 'thumbnail_create_url' in request_body:
thumbnail_create_url = request_body['thumbnail_create_url']
elif 'layers' in request_body:
layers = request_body['layers']

wms_endpoint = getattr(ogc_server_settings, "WMS_ENDPOINT") or 'ows'
wms_version = getattr(ogc_server_settings, "WMS_VERSION") or '1.1.1'
wms_format = getattr(ogc_server_settings, "WMS_FORMAT") or 'image/png8'

params = {
'service': 'WMS',
'version': wms_version,
'request': 'GetMap',
'layers': layers,
'format': wms_format,
# 'TIME': '-99999999999-01-01T00:00:00.0Z/99999999999-01-01T00:00:00.0Z'
}
_p = "&".join("%s=%s" % item for item in params.items())

import posixpath
thumbnail_create_url = posixpath.join(
ogc_server_settings.LOCATION,
wms_endpoint) + "?" + _p

# Compute Bounds
wgs84_bbox = decimal_encode(
bbox_to_projection([float(coord) for coord in request_body['bbox']] + [request_body['srid'], ],
target_srid=4326)[:4])

# Build Image Request Template
_img_request_template = "<div style='overflow: hidden; position:absolute; \
top:0px; left:0px; height: {height}px; width: {width}px;'> \
\n".format(height=height, width=width)

# Fetch XYZ tiles
bounds = wgs84_bbox[0:4]
zoom = bounds_to_zoom_level(bounds, width, height)

t_ll = mercantile.tile(_v(bounds[0], x=True), _v(bounds[1], x=False), zoom, truncate=True)
t_ur = mercantile.tile(_v(bounds[2], x=True), _v(bounds[3], x=False), zoom, truncate=True)
xmin, ymax = t_ll.x, t_ll.y
xmax, ymin = t_ur.x, t_ur.y

for xtile in range(xmin, xmax+1):
for ytile in range(ymin, ymax+1):
box = [(xtile-xmin)*256, (ytile-ymin)*255]
if smurl:
imgurl = smurl.format(z=zoom, x=xtile, y=ytile)
_img_request_template += _img_src_template.format(ogc_location=imgurl,
height=256, width=256,
left=box[0], top=box[1])

xy_bounds = mercantile.xy_bounds(mercantile.Tile(xtile, ytile, zoom))
params = {
'width': 256,
'height': 256,
'transparent': True,
'bbox': ",".join([str(xy_bounds.left), str(xy_bounds.bottom),
str(xy_bounds.right), str(xy_bounds.top)]),
'crs': 'EPSG:3857'
}
_p = "&".join("%s=%s" % item for item in params.items())

_img_request_template += \
_img_src_template.format(ogc_location=(thumbnail_create_url + '&' + _p),
height=256, width=256,
left=box[0], top=box[1])
_img_request_template += "</div>"
image = _render_thumbnail(_img_request_template, width=width, height=height)
return image


def _fixup_ows_url(thumb_spec):
# @HACK - for whatever reason, a map's maplayers ows_url contains only /geoserver/wms
# so rendering of thumbnails fails - replace those uri's with full geoserver URL
Expand Down
2 changes: 1 addition & 1 deletion geonode/geoserver/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -617,4 +617,4 @@ def geoserver_pre_save_maplayer(instance, sender, **kwargs):

def geoserver_post_save_map(instance, sender, **kwargs):
instance.set_missing_info()
create_gs_thumbnail(instance, overwrite=False)
create_gs_thumbnail(instance, overwrite=True)
17 changes: 11 additions & 6 deletions geonode/geoserver/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,11 +496,16 @@ def strip_prefix(path, prefix):
posixpath.join(workspace, layername, downstream_path, path))

if downstream_path in ('rest/styles') and len(request.body) > 0:
# Lets try
# http://localhost:8080/geoserver/rest/workspaces/<ws>/styles/<style>.xml
_url = str("".join([ogc_server_settings.LOCATION,
'rest/workspaces/', ws, '/styles',
path]))
if ws:
# Lets try
# http://localhost:8080/geoserver/rest/workspaces/<ws>/styles/<style>.xml
_url = str("".join([ogc_server_settings.LOCATION,
'rest/workspaces/', ws, '/styles',
path]))
else:
_url = str("".join([ogc_server_settings.LOCATION,
'rest/styles',
path]))
raw_url = _url

if downstream_path in 'ows' and (
Expand Down Expand Up @@ -545,7 +550,7 @@ def _response_callback(**kwargs):
logger.debug(
'Updating thumbnail for layer with uuid %s' %
layer.uuid)
create_gs_thumbnail(layer, True)
create_gs_thumbnail(layer, overwrite=True)

# Replace Proxy URL
if content_type in ('application/xml', 'text/xml', 'text/plain'):
Expand Down
86 changes: 56 additions & 30 deletions geonode/layers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@
from geonode.layers.metadata import set_metadata
from geonode.utils import (http_client, check_ogc_backend,
unzip_file, extract_tarfile)
from ..geoserver.helpers import ogc_server_settings # set_layer_style
from ..geoserver.helpers import (ogc_server_settings,
_prepare_thumbanil_body_from_opts)

import tarfile

Expand Down Expand Up @@ -880,7 +881,8 @@ def upload(incoming, user=None, overwrite=False,


def create_thumbnail(instance, thumbnail_remote_url, thumbnail_create_url=None,
check_bbox=False, ogc_client=None, overwrite=False):
check_bbox=False, ogc_client=None, overwrite=False,
width=240, height=200):
thumbnail_dir = os.path.join(settings.MEDIA_ROOT, 'thumbs')
if not os.path.exists(thumbnail_dir):
os.makedirs(thumbnail_dir)
Expand All @@ -890,9 +892,7 @@ def create_thumbnail(instance, thumbnail_remote_url, thumbnail_create_url=None,
elif isinstance(instance, Map):
thumbnail_name = 'map-%s-thumb.png' % instance.uuid
thumbnail_path = os.path.join(thumbnail_dir, thumbnail_name)
if overwrite is True or storage.exists(thumbnail_path) is False:
if not ogc_client:
ogc_client = http_client
if overwrite or not storage.exists(thumbnail_path):
BBOX_DIFFERENCE_THRESHOLD = 1e-5

if not thumbnail_create_url:
Expand Down Expand Up @@ -930,21 +930,55 @@ def create_thumbnail(instance, thumbnail_remote_url, thumbnail_create_url=None,
.update(thumbnail_url=thumbnail_remote_url)

# Download thumbnail and save it locally.
try:
resp, image = ogc_client.request(thumbnail_create_url)
if 'ServiceException' in image or \
resp.status < 200 or resp.status > 299:
msg = 'Unable to obtain thumbnail: %s' % image
raise Exception(msg)
except BaseException:
import traceback
logger.debug(traceback.format_exc())

# Replace error message with None.
image = None
if not ogc_client and not check_ogc_backend(geoserver.BACKEND_PACKAGE):
ogc_client = http_client

if image is not None:
instance.save_thumbnail(thumbnail_name, image=image)
if ogc_client:
try:
params = {
'width': width,
'height': height
}
# Add the bbox param only if the bbox is different to [None, None,
# None, None]
if None not in instance.bbox:
params['bbox'] = instance.bbox_string
params['crs'] = instance.srid

_p = "&".join("%s=%s" % item for item in params.items())
resp, image = ogc_client.request(thumbnail_create_url + '&' + _p)
if 'ServiceException' in image or \
resp.status < 200 or resp.status > 299:
msg = 'Unable to obtain thumbnail: %s' % image
raise Exception(msg)
except BaseException:
import traceback
logger.debug(traceback.format_exc())

# Replace error message with None.
image = None
elif check_ogc_backend(geoserver.BACKEND_PACKAGE) and instance.bbox:
instance_bbox = instance.bbox[0:4]
request_body = {
'bbox': [str(coord) for coord in instance_bbox],
'srid': instance.srid,
'width': width,
'height': height
}

if thumbnail_create_url:
request_body['thumbnail_create_url'] = thumbnail_create_url
elif instance.alternate:
request_body['layers'] = instance.alternate,

image = _prepare_thumbanil_body_from_opts(request_body)

if image is not None:
instance.save_thumbnail(thumbnail_name, image=image)
else:
msg = 'Unable to obtain thumbnail for: %s' % instance
logger.error(msg)
# raise Exception(msg)


# this is the original implementation of create_gs_thumbnail()
Expand All @@ -971,17 +1005,9 @@ def create_gs_thumbnail_geonode(instance, overwrite=False, check_bbox=False):
'request': 'GetMap',
'layers': layers,
'format': wms_format,
'width': 200,
'height': 150
# 'TIME': '-99999999999-01-01T00:00:00.0Z/99999999999-01-01T00:00:00.0Z'
}

# Add the bbox param only if the bbox is different to [None, None,
# None, None]
if None not in instance.bbox:
params['bbox'] = instance.bbox_string
params['crs'] = instance.srid

# Avoid using urllib.urlencode here because it breaks the url.
# commas and slashes in values get encoded and then cause trouble
# with the WMS parser.
Expand All @@ -995,7 +1021,7 @@ def create_gs_thumbnail_geonode(instance, overwrite=False, check_bbox=False):
ogc_server_settings.LOCATION,
wms_endpoint) + "?" + _p
create_thumbnail(instance, thumbnail_remote_url, thumbnail_create_url,
ogc_client=http_client, overwrite=overwrite, check_bbox=check_bbox)
overwrite=overwrite, check_bbox=check_bbox)


def delete_orphaned_layers():
Expand All @@ -1004,8 +1030,8 @@ def delete_orphaned_layers():
for filename in os.listdir(layer_path):
fn = os.path.join(layer_path, filename)
if LayerFile.objects.filter(file__icontains=filename).count() == 0:
print 'Removing orphan layer file %s' % fn
logger.info('Removing orphan layer file %s' % fn)
try:
os.remove(fn)
except OSError:
print 'Could not delete file %s' % fn
logger.info('Could not delete file %s' % fn)

0 comments on commit f43190c

Please sign in to comment.