Ejemplo n.º 1
0
    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
Ejemplo n.º 2
0
    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'
            )
Ejemplo n.º 3
0
    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.')
Ejemplo n.º 4
0
    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)
Ejemplo n.º 5
0
    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)
Ejemplo n.º 6
0
    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
Ejemplo n.º 7
0
 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)
Ejemplo n.º 8
0
    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)
Ejemplo n.º 9
0
    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)
Ejemplo n.º 10
0
    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.'
                )
Ejemplo n.º 11
0
    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)
Ejemplo n.º 12
0
    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)
Ejemplo n.º 13
0
    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))
Ejemplo n.º 14
0
 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