#!/usr/bin/env python # # heron.cgi - generic RESTful services for Heron # # This CGI provides Heron services that can only/better be handled # within a server. The setup is generic: a parameter 'action' determines # which function is to be called. The other parameters are specific to # each handler. import cgi import cgitb import base64 import zipfile import subprocess import os import tempfile cgitb.enable() # Get form/query params params = cgi.FieldStorage() def param_available(param_names): for param_name in param_names: if param_name not in params: print('Content-Type: text/html') print('') print('

Heron Export - Error

') print('Please supply query parameter %s.' % param_name ) return False return True def findshapelayer(zip_file_path): # print("readzipfile content=" + str(fz.namelist())) zip_file = zipfile.ZipFile(zip_file_path, "r") for file_path in zip_file.namelist(): ext = file_path.split('.') # print("readzipfile: " + naam) if len(ext) == 2: if ext[1] == 'shp': layer_name = ext[0] if '/' in layer_name: layer_name = layer_name.split('/') layer_name = layer_name[len(layer_name) - 1] return '/' + file_path, layer_name # Convert a CGI file_item to given format using ogr2ogr # return result as data blob # TODO generalize e.g. s_srs and t_srs def ogr2ogr(file_item, format_out, file_ext): # A bit tricky: CGI gives us a file item, but the file # is already open, so we need to do the following # 1. write form value data to temp zip file (in_file) # 2. provide writeable tempfile (out_file) # 3. call ogr2ogr # 4. read and return the data from the out_file in_fd, in_file = tempfile.mkstemp(prefix='hr_', suffix=file_ext) # Use tempfile just for filename, we will close and remove file # and use the file path/name, wasteful but necessary out_fd, out_file = tempfile.mkstemp(prefix='hr_', suffix='.ogr') in_ogr_file = in_file try: os.write(in_fd, file_item.value) os.close(in_fd) os.close(out_fd) os.remove(out_file) # Assume ogr2ogr input file is temp file 'in_file if file_ext == '.zip': # Zipped Shapefile: use /vsizip// virtual path. # Find the first layer in the .zip file and the path, we construct # an ogr /vsizip path from that layer_path, layer_name = findshapelayer(in_file) in_ogr_file = '/vsizip/' + in_file + layer_path else: if file_ext == '.csv': # Tricky: .csv needs an OGR VRT file that points to # the actual .csv file and contains info mainly for how to create # for example geometries from columns. # So we create a temp VRT file that points to our .csv, indicating # that the X,Y (or lower x,y) contain a Point geometry # Also some older versions of ogr2ogr may be picky on leading spaces in the VRT file. layer_name, ext = os.path.splitext(os.path.basename(in_file)) ogr_vrt = ''' %s wkbPoint ''' % (layer_name, in_file) # Create temp VRT file and fill in_vrt_fd, in_vrt_file = tempfile.mkstemp(prefix='hr_', suffix='.vrt') os.write(in_vrt_fd, ogr_vrt.strip()) os.close(in_vrt_fd) in_ogr_file = in_vrt_file # Entire ogr2ogr command line # Make ogr2ogr command line, use separator | to deal with quotes etc. cmd_tmpl = 'ogr2ogr|-f|%s|%s|%s' cmd = cmd_tmpl % (format_out, out_file, in_ogr_file) cmd = cmd.split('|') # Call ogr2ogr ret_code = subprocess.call(cmd) # print 'ret_code = %d' % ret_code # Fetch data result from output file # TODO with CGI we should be able to output to stdout thus directly to client out_fd = open(out_file) data_out = out_fd.read() out_fd.close() except: data_out = 'Error in ogr2ogr in Heron.cgi ' + in_file # Cleanup os.remove(in_file) if os.path.isfile(out_file): os.remove(out_file) if os.path.isfile(in_ogr_file): os.remove(in_ogr_file) return data_out # Echo data back to client forcing a download to file in the browser. def download(): if not param_available(['mime', 'data', 'filename']): return # Get the form-based data values filename = params.getvalue('filename') mime = params.getvalue('mime') data = params.getvalue('data') encoding = params.getvalue('encoding', 'base64') if encoding == 'base64': data = base64.b64decode(data) # Echo back to client print('Content-Type: %s' % mime) # Forces download in browser print('Content-Disposition: attachment; filename="%s"' % filename) print('') print(data) # Echo uploaded file back to client as data. def upload(): if not param_available(['mime', 'file']): return # Get the form-based data values mime = params.getvalue('mime') file_item = params['file'] encoding = params.getvalue('encoding', 'none') # Start echo back print('Content-Type: %s' % mime) print('') # Test if the file was uploaded if file_item.filename: # strip leading path from file name to avoid directory traversal attacks # fn = os.path.basename(fileitem.filename) # open('files/' + fn, 'wb').write(fileitem.file.read()) # Echo back file content to client # print tempfile.gettempprefix() # print file_item.filename # file_path = os.path.join(self.path, file_item.filename) # temp_file = tempfile.TemporaryFile() # file_path = file_item.file.name data = file_item.value # if the upload is a .zip file we assume a zipped ESRI Shapefile # we convert it to GeoJSON, such that the client can read it # The config in the Heron client (Upload or Editor) should then have an entry like: # {name: 'ESRI Shapefile (1 laag, gezipped)', fileExt: '.zip', mimeType: 'text/plain', formatter: 'OpenLayers.Format.GeoJSON'} f, file_ext = os.path.splitext(file_item.filename.lower()) if file_ext == '.zip' or file_ext == '.csv': data = ogr2ogr(file_item, 'GeoJSON', file_ext) if encoding == 'escape': data = cgi.escape(data) print(data) else: print(' ') # Action handlers: jump table with function pointers handlers = { 'download': download, 'upload': upload } handlers[params.getvalue('action', 'download')]()