def get_ids(self): if self._layer_ids is not None: return self._layer_ids if 'layer' in self.kwargs: # For tms requests, construct simple ids dictionary. data = self.kwargs.get('layer') # Determine query paremeter type try: layer_id = int(data) except ValueError: query = Q(rasterfile__contains='rasters/' + data) layer_id = get_object_or_404(RasterLayer, query).id # For TMS tile request, get the layer id from the url. self._layer_ids = {'x': layer_id} else: # For algebra requests, get the layer ids from the query parameter. ids = self.request.GET.get('layers', '').split(',') # Check if layer parameter is valid if not len(ids) or not all('=' in idx for idx in ids): raise RasterAlgebraException('Layer parameter is not valid.') # Split id/name input pairs ids = [idx.split('=') for idx in ids] # Convert ids to integer try: ids = {idx[0]: int(idx[1]) for idx in ids} except ValueError: raise RasterAlgebraException('Layer parameter is not valid.') self._layer_ids = ids return self._layer_ids
def check_aligned(self, rasters): """ Assert that all input rasters are properly aligned. """ if not len(set([x.srs.srid for x in rasters])) == 1: raise RasterAlgebraException( 'Raster aligment check failed: SRIDs not all the same') gt = rasters[0].geotransform if any([gt != rast.geotransform for rast in rasters[1:]]): raise RasterAlgebraException( 'Raster aligment check failed: geotransform arrays are not all the same' )
def get(self, request, *args, **kwargs): # Get layer ids ids = self.get_ids() # Get raster data as 1D arrays and store in dict that can be used # for formula evaluation. data = {} for name, layerid in ids.items(): tile = self.get_tile(layerid) if tile: data[name] = tile else: # Create empty image if any layer misses the required tile img = Image.new("RGBA", (WEB_MERCATOR_TILESIZE, WEB_MERCATOR_TILESIZE), (0, 0, 0, 0)) return self.write_img_to_response(img, {}) # Get formula from request formula = request.GET.get('formula', None) # Dispatch by request type. If a formula was provided, use raster # algebra otherwise look for rgb request. if formula: return self.get_algebra(data, formula) elif 'r' in data and 'g' in data and 'b' in data: return self.get_rgb(data) else: raise RasterAlgebraException( 'Specify raster algebra formula or provide rgb layer keys.')
def get_algebra(self, data, formula): parser = RasterAlgebraParser() # Evaluate raster algebra expression, return 400 if not successful try: # Evaluate raster algebra expression result = parser.evaluate_raster_algebra(data, formula) except: raise RasterAlgebraException('Failed to evaluate raster algebra.') # Get array from algebra result if result.bands[0].nodata_value is None: result = result.bands[0].data() else: result = numpy.ma.masked_values( result.bands[0].data(), result.bands[0].nodata_value, ) # Get colormap. colormap = self.get_colormap() # Render tile using the legend data img, stats = band_data_to_image(result, colormap) # Return rendered image return self.write_img_to_response(img, stats)
def evaluate(self, data={}, formula=None): """ Evaluate the input data using the current formula expression stack. The formula is stored as attribute and can be re-evaluated with several input data sets on an existing parser. """ if formula: self.set_formula(formula) if not self.formula: raise RasterAlgebraException('Formula not specified.') # Store data for variables self.variable_map = data # Check and convert input data self.prepare_data() # Reset expression stack self.expr_stack = [] # Populate the expression stack self.bnf.parseString(self.formula) # Evaluate stack on data return self.evaluate_stack(self.expr_stack)
def get_ids(self): # Get layer ids ids = self.request.GET.get('layers', '').split(',') # Check if layer parameter is valid if not len(ids) or not all('=' in idx for idx in ids): raise RasterAlgebraException('Layer parameter is not valid.') # Split id/name input pairs ids = [idx.split('=') for idx in ids] # Convert ids to integer try: ids = {idx[0]: int(idx[1]) for idx in ids} except ValueError: raise RasterAlgebraException('Layer parameter is not valid.') return ids
def get_mask(data, operator): # Make sure the right operator is used if operator not in (const.EQUAL, const.NOT_EQUAL): raise RasterAlgebraException( 'NULL can only be used with "==" or "!=" operators.') # Get mask if numpy.ma.is_masked(data): return data.mask # If there is no mask, all values are not null return numpy.zeros(data.shape, dtype=numpy.bool)
def get_algebra(self, data, formula): parser = RasterAlgebraParser() # Evaluate raster algebra expression, return 400 if not successful try: # Evaluate raster algebra expression result = parser.evaluate_raster_algebra(data, formula) except: raise RasterAlgebraException('Failed to evaluate raster algebra.') # For pixel value requests, return result as json. if self.is_pixel_request: xcoord = float(self.kwargs.get('xcoord')) ycoord = float(self.kwargs.get('ycoord')) val = pixel_value_from_point(result, [xcoord, ycoord]) return HttpResponse( json.dumps({ 'x': xcoord, 'y': ycoord, 'value': val }), content_type='application/json', ) # For tif requests, skip colormap and return georeferenced tif file. if self.kwargs.get('frmt') == 'tif': vsi_path = os.path.join(VSI_FILESYSTEM_BASE_PATH, str(uuid.uuid4())) rast = result.warp({ 'name': vsi_path, 'driver': 'tif', 'compress': 'DEFLATE', }) content_type = IMG_FORMATS['tif'][1] return HttpResponse(rast.vsi_buffer, content_type) # Get array from algebra result if result.bands[0].nodata_value is None: result = result.bands[0].data() else: result = numpy.ma.masked_values( result.bands[0].data(), result.bands[0].nodata_value, ) # Get colormap. colormap = self.get_colormap() # Render tile using the legend data img, stats = band_data_to_image(result, colormap) # Return rendered image return self.write_img_to_response(img, stats)
def prepare_data(self): """ Basic checks and conversion of input data. """ for key, var in self.variable_map.items(): # Keywords are not allowed as variables, variables can not start or # end with separator. if keyword.iskeyword(key) or key != key.strip( const.VARIABLE_NAME_SEPARATOR): raise RasterAlgebraException( 'Invalid variable name found: "{}".'.format(key)) # Convert all data to numpy arrays if not isinstance(var, numpy.ndarray): self.variable_map[key] = numpy.array(var)
def get(self, request, *args, **kwargs): # Get layer ids ids = self.get_ids() # Prepare unique list of layer ids to be efficient if the same layer # is used multiple times (for band access for instance). layerids = set(ids.values()) # Get the tiles for each unique layer. tiles = {} for layerid in layerids: tile = self.get_tile(layerid) if tile: tiles[layerid] = tile else: # Create empty image if any layer misses the required tile img = Image.new("RGBA", (WEB_MERCATOR_TILESIZE, WEB_MERCATOR_TILESIZE), (0, 0, 0, 0)) return self.write_img_to_response(img, {}) # Map tiles to a dict with formula names as keys. data = {} for name, layerid in ids.items(): data[name] = tiles[layerid] # Get formula from request if 'layer' in self.kwargs: # Set the formula to trivial for TMS requests. formula = 'x' else: formula = request.GET.get('formula', None) # Dispatch by request type. If a formula was provided, use raster # algebra otherwise look for rgb request. if formula: return self.get_algebra(data, formula) else: keys = [key.split(BAND_INDEX_SEPARATOR)[0] for key in data.keys()] if 'r' in keys and 'g' in keys and 'b' in keys: return self.get_rgb(data) else: raise RasterAlgebraException( 'Specify raster algebra formula or provide rgb layer keys.' )
def get_algebra(self, data, formula): parser = RasterAlgebraParser() # Evaluate raster algebra expression, return 400 if not successful try: # Evaluate raster algebra expression result = parser.evaluate_raster_algebra(data, formula) except: raise RasterAlgebraException('Failed to evaluate raster algebra.') # For pixel value requests, return result as json. if self.is_pixel_request: xcoord = float(self.kwargs.get('xcoord')) ycoord = float(self.kwargs.get('ycoord')) val = pixel_value_from_point(result, [xcoord, ycoord]) return HttpResponse( json.dumps({ 'x': xcoord, 'y': ycoord, 'value': val }), content_type='application/json', ) # Get array from algebra result if result.bands[0].nodata_value is None: result = result.bands[0].data() else: result = numpy.ma.masked_values( result.bands[0].data(), result.bands[0].nodata_value, ) # Get colormap. colormap = self.get_colormap() # Render tile using the legend data img, stats = band_data_to_image(result, colormap) # Return rendered image return self.write_img_to_response(img, stats)
def get_algebra(self, data, formula): parser = RasterAlgebraParser() # Evaluate raster algebra expression, return 400 if not successful try: # Evaluate raster algebra expression result = parser.evaluate_raster_algebra(data, formula) except: raise RasterAlgebraException('Failed to evaluate raster algebra.') # Get array from algebra result result = numpy.ma.masked_values( result.bands[0].data(), result.bands[0].nodata_value, ) # Render tile colormap = self.get_colormap() if colormap: # Render tile using the legend data img, stats = band_data_to_image(result, colormap) else: # Scale to grayscale rgb (can be colorscheme later on) result = result.astype('float').ravel() result = 255 * (result - numpy.min(result)) / (numpy.max(result) - numpy.min(result)) # Create rgba matrix from grayscale array result = numpy.array( (result, result, result, numpy.repeat(255, len(result)))).T rgba = result.reshape(WEB_MERCATOR_TILESIZE, WEB_MERCATOR_TILESIZE, 4).astype('uint8') # Create image from array img = Image.fromarray(rgba) stats = {} # Return rendered image return self.write_img_to_response(img, stats)
def evaluate_stack(self, stack): """ Evaluate a stack element. """ op = stack.pop() if op in const.UNARY_OPERATOR_MAP: return const.UNARY_OPERATOR_MAP[op](self.evaluate_stack(stack)) elif op in const.OPERATOR_MAP: op2 = self.evaluate_stack(stack) op1 = self.evaluate_stack(stack) # Handle null case if isinstance(op1, str) and op1 == const.NULL: op2 = self.get_mask(op2, op) op1 = True elif isinstance(op2, str) and op2 == const.NULL: op1 = self.get_mask(op1, op) op2 = True return const.OPERATOR_MAP[op](op1, op2) elif op in const.FUNCTION_MAP: return const.FUNCTION_MAP[op](self.evaluate_stack(stack)) elif op in const.KEYWORD_MAP: return const.KEYWORD_MAP[op] elif op in self.variable_map: return self.variable_map[op] else: try: return numpy.array(op, dtype=const.ALGEBRA_PIXEL_TYPE_NUMPY) except ValueError: raise RasterAlgebraException( 'Found an undeclared variable "{0}" in formula.'.format( op))
def get(self, request): # Initiate algebra parser parser = RasterAlgebraParser() # Get formula from request formula = request.GET.get('formula') # Get id list from request ids = self.get_ids() # Compute tile index range zoom, xmin, ymin, xmax, ymax = self.get_tile_range() # Check maximum size of target raster in pixels max_pixels = getattr(settings, 'RASTER_EXPORT_MAX_PIXELS', EXPORT_MAX_PIXELS) if WEB_MERCATOR_TILESIZE * (xmax - xmin) * WEB_MERCATOR_TILESIZE * ( ymax - ymin) > max_pixels: raise RasterAlgebraException('Export raster too large.') # Construct an empty raster with the output dimensions result_raster = self.construct_raster(zoom, xmin, xmax, ymin, ymax) target = result_raster.bands[0] # Get raster data as 1D arrays and store in dict that can be used # for formula evaluation. for xindex, x in enumerate(range(xmin, xmax + 1)): for yindex, y in enumerate(range(ymin, ymax + 1)): data = {} for name, layerid in ids.items(): tile = get_raster_tile(layerid, zoom, x, y) if tile: data[name] = tile # Ignore this tile if data is not found for all layers if len(data) != len(ids): continue # Evaluate raster algebra expression, return 400 if not successful try: # Evaluate raster algebra expression tile_result = parser.evaluate_raster_algebra(data, formula) except: raise RasterAlgebraException( 'Failed to evaluate raster algebra.') # Update nodata value on target target.nodata_value = tile_result.bands[0].nodata_value # Update results raster with algebra target.data( data=tile_result.bands[0].data(), size=(WEB_MERCATOR_TILESIZE, WEB_MERCATOR_TILESIZE), offset=(xindex * WEB_MERCATOR_TILESIZE, yindex * WEB_MERCATOR_TILESIZE), ) # Create filename base with datetime stamp filename_base = 'algebra_export' # Add name slug to filename if provided if request.GET.get('filename', ''): # Sluggify name slug = slugify(request.GET.get('filename')) # Remove all unwanted characters slug = "".join([c for c in slug if re.match(r'\w|\-', c)]) # Limit length of custom name slug slug = slug[:MAX_EXPORT_NAME_LENGTH] # Add name slug to filename base filename_base += '_' + slug filename_base += '_{0}'.format( datetime.now().strftime('%Y_%m_%d_%H_%M')) # Compress resulting raster file into a zip archive raster_workdir = getattr(settings, 'RASTER_WORKDIR', None) dest = NamedTemporaryFile(dir=raster_workdir, suffix='.zip') dest_zip = zipfile.ZipFile(dest.name, 'w', allowZip64=True) dest_zip.write( filename=self.exportfile.name, arcname=filename_base + '.tif', compress_type=zipfile.ZIP_DEFLATED, ) # Write README.txt and COLORMAP.txt files to zip file self.write_readme(dest_zip) self.write_colormap(dest_zip) # Close zip file before returning dest_zip.close() # Create file based response containing zip file and return for download response = FileResponse(open(dest.name, 'rb'), content_type='application/zip') response['Content-Disposition'] = 'attachment; filename="{0}"'.format( filename_base + '.zip') return response