예제 #1
0
 def test_issue157(self):
     """Verify that we get the error class name back - issue #157
        .. seealso:: https://github.com/AIFDR/inasafe/issues/121
     """
     try:
         bbox_intersection('aoeu', 'oaeu', [])
     except BoundingBoxError, e:
         myMessage = getExceptionWithStacktrace(e, theHtml=False)
         assert 'BoundingBoxError : Western' in myMessage, myMessage
예제 #2
0
 def test_issue157(self):
     """Verify that we get the error class name back - issue #157
        .. seealso:: https://github.com/AIFDR/inasafe/issues/121
     """
     try:
         bbox_intersection('aoeu', 'oaeu', [])
     except VerificationError, e:
         myMessage = getExceptionWithStacktrace(e, html=False)
         assert 'VerificationError : Western' in myMessage, myMessage
예제 #3
0
    def test_stacktrace_html(self):
        """Stack traces can be caught and rendered as html
        """

        # This is about general exception handling, so ok to use catch-all
        # pylint: disable=W0703
        try:
            bbox_intersection('aoeu', 'oaeu', [])
        except Exception, e:
            # Display message and traceback

            myMessage = getExceptionWithStacktrace(e, theHtml=False)
            #print myMessage
            assert str(e) in myMessage
            assert 'line' in myMessage
            assert 'File' in myMessage

            myMessage = getExceptionWithStacktrace(e, theHtml=True)
            assert str(e) in myMessage
            assert '<pre id="traceback"' in myMessage
            assert 'line' in myMessage
            assert 'File' in myMessage
예제 #4
0
    def test_stacktrace_html(self):
        """Stack traces can be caught and rendered as html
        """

        # This is about general exception handling, so ok to use catch-all
        # pylint: disable=W0703
        try:
            bbox_intersection('aoeu', 'oaeu', [])
        except Exception, e:
            # Display message and traceback

            myMessage = getExceptionWithStacktrace(e, html=False)
            #print myMessage
            assert str(e) in myMessage
            assert 'line' in myMessage
            assert 'File' in myMessage

            myMessage = getExceptionWithStacktrace(e, html=True)
            assert str(e) in myMessage
            assert '<pre id="traceback"' in myMessage
            assert 'line' in myMessage
            assert 'File' in myMessage
예제 #5
0
def getOptimalExtent(theHazardGeoExtent,
                     theExposureGeoExtent,
                     theViewportGeoExtent=None):
    """ A helper function to determine what the optimal extent is.
    Optimal extent should be considered as the intersection between
    the three inputs. The inasafe library will perform various checks
    to ensure that the extent is tenable, includes data from both
    etc.

    This is just a thin wrapper around safe.api.bbox_intersection.

    Typically the result of this function will be used to clip
    input layers to a commone extent before processing.

    Args:

        * theHazardGeoExtent - an array representing the hazard layer
           extents in the form [xmin, ymin, xmax, ymax]. It is assumed
           that the coordinates are in EPSG:4326 although currently
           no checks are made to enforce this.
        * theExposureGeoExtent - an array representing the exposure layer
           extents in the form [xmin, ymin, xmax, ymax]. It is assumed
           that the coordinates are in EPSG:4326 although currently
           no checks are made to enforce this.
        * theViewPortGeoExtent (optional) - an array representing the viewport
           extents in the form [xmin, ymin, xmax, ymax]. It is assumed
           that the coordinates are in EPSG:4326 although currently
           no checks are made to enforce this.

       ..note:: We do minimal checking as the inasafe library takes
         care of it for us.

    Returns:
       An array containing an extent in the form [xmin, ymin, xmax, ymax]
       e.g.::

          [100.03, -1.14, 100.81, -0.73]

    Raises:
        Any exceptions raised by the InaSAFE library will be propogated.
    """

    #
    myMessage = tr('theHazardGeoExtent or theExposureGeoExtent cannot be None.'
                   'Found: /ntheHazardGeoExtent: %s '
                   '/ntheExposureGeoExtent: %s' %
                   (theHazardGeoExtent, theExposureGeoExtent))

    if (theHazardGeoExtent is None) or (theExposureGeoExtent is None):
        raise BoundingBoxError(myMessage)

    # .. note:: The bbox_intersection function below assumes that
    #           all inputs are in EPSG:4326
    myOptimalExtent = \
        bbox_intersection(theHazardGeoExtent,
                          theExposureGeoExtent,
                          theViewportGeoExtent)

    if myOptimalExtent is None:
        # Bounding boxes did not overlap
        myMessage = \
            tr('Bounding boxes of hazard data, exposure data '
               'and viewport did not overlap, so no computation was '
               'done. Please make sure you pan to where the data is and '
               'that hazard and exposure data overlaps.')
        raise InsufficientOverlapError(myMessage)

    return myOptimalExtent
예제 #6
0
                'be a sequence of the '
               'form [west, south, east, north]' % (str(x),
                                                    str(type(x))[1:-1]))
        try:
            list(x)
        except:
            raise InvalidBoundingBoxException(myMessage)
        try:
            verify(len(x) == 4, myMessage)
        except VerificationError, e:
            raise InvalidBoundingBoxException(str(e))

    # .. note:: The bbox_intersection function below assumes that
    #           all inputs are in EPSG:4326
    myOptimalExtent = bbox_intersection(theHazardGeoExtent,
                                        theExposureGeoExtent,
                                        theViewportGeoExtent)
    if myOptimalExtent is None:
        # Bounding boxes did not overlap
        myMessage = tr('Bounding boxes of hazard data, exposure data '
               'and viewport did not overlap, so no computation was '
               'done. Please make sure you pan to where the data is and '
               'that hazard and exposure data overlaps.')
        raise InsufficientOverlapException(myMessage)

    return myOptimalExtent


def getBufferedExtent(theGeoExtent, theCellSize):
    """Grow bounding box with one unit of resolution in each direction.
예제 #7
0
def getOptimalExtent(theHazardGeoExtent,
                     theExposureGeoExtent,
                     theViewportGeoExtent=None):
    """ A helper function to determine what the optimal extent is.
    Optimal extent should be considered as the intersection between
    the three inputs. The inasafe library will perform various checks
    to ensure that the extent is tenable, includes data from both
    etc.

    This is just a thin wrapper around safe.api.bbox_intersection.

    Typically the result of this function will be used to clip
    input layers to a commone extent before processing.

    Args:

        * theHazardGeoExtent - an array representing the hazard layer
           extents in the form [xmin, ymin, xmax, ymax]. It is assumed
           that the coordinates are in EPSG:4326 although currently
           no checks are made to enforce this.
        * theExposureGeoExtent - an array representing the exposure layer
           extents in the form [xmin, ymin, xmax, ymax]. It is assumed
           that the coordinates are in EPSG:4326 although currently
           no checks are made to enforce this.
        * theViewPortGeoExtent (optional) - an array representing the viewport
           extents in the form [xmin, ymin, xmax, ymax]. It is assumed
           that the coordinates are in EPSG:4326 although currently
           no checks are made to enforce this.

       ..note:: We do minimal checking as the inasafe library takes
         care of it for us.

    Returns:
       An array containing an extent in the form [xmin, ymin, xmax, ymax]
       e.g.::

          [100.03, -1.14, 100.81, -0.73]

    Raises:
        Any exceptions raised by the InaSAFE library will be propogated.
    """

    #
    message = tr('theHazardGeoExtent or theExposureGeoExtent cannot be None.'
                 'Found: /ntheHazardGeoExtent: %s '
                 '/ntheExposureGeoExtent: %s' %
                 (theHazardGeoExtent, theExposureGeoExtent))

    if (theHazardGeoExtent is None) or (theExposureGeoExtent is None):
        raise BoundingBoxError(message)

    # .. note:: The bbox_intersection function below assumes that
    #           all inputs are in EPSG:4326
    myOptimalExtent = \
        bbox_intersection(theHazardGeoExtent,
                          theExposureGeoExtent,
                          theViewportGeoExtent)

    if myOptimalExtent is None:
        # Bounding boxes did not overlap
        message = \
            tr('Bounding boxes of hazard data, exposure data '
               'and viewport did not overlap, so no computation was '
               'done. Please make sure you pan to where the data is and '
               'that hazard and exposure data overlaps.')
        raise InsufficientOverlapError(message)

    return myOptimalExtent
예제 #8
0
    def get(self):
        #        try:
        exposure_id = self.get_argument('e')
        hazard_id = self.get_argument('h')
        impact_name = 'impact-e%s-h%s' % (exposure_id, hazard_id)
        if exposure_id and hazard_id:
            # First check if the impact already exists in the cache
            try:  # try to connect to the redis cache
                redis_server = redis.Redis()
                cache = True
                print 'Successfully connected to redis!'
            except:
                # This is just a flag that will be used later on
                print "I couldn't connect to redis"
                cache = False
            else:
                # If the impact exists, get it from the cache and return
                if redis_server.exists(impact_name):
                    print 'Entry exists in cache!'
                    writeout = redis_server.get(impact_name)
                    self.set_header('Content-Type', 'application/javascript')
                    self.write(writeout)
                    return

            # Query the db and calculate if it doesn't
            try:  #try connecting to the pg database
                conn = psycopg2.connect(
                    "dbname='dev' user='******' password='******'")
                print 'Successfully connected to postgres!'
            except:
                writeout = 'Could not connect to the database!'
            else:
                # create a cursor
                cursor = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
                try:
                    #1. Query the db for the layers
                    query = 'SELECT shapefile FROM layers'+\
                                ' WHERE id = %s' % exposure_id
                    cursor.execute(query)
                    exposure = cursor.fetchone()
                    query = 'SELECT shapefile FROM layers'+\
                                ' WHERE id = %s' % hazard_id
                    cursor.execute(query)
                    hazard = cursor.fetchone()
                except:
                    writeout = 'There was something wrong with your query'
                    conn.rollback()
                else:
                    if exposure and hazard:
                        # Pass the shapefile (paths) to read_layer
                        try:
                            exposure_layer = read_layer(exposure['shapefile'])
                            hazard_layer = read_layer(hazard['shapefile'])
                        except:
                            writeout = 'Something went wrong when reading the layers'
                        # Keywords
                        exposure_dict = exposure_layer.get_keywords()
                        hazard_dict = hazard_layer.get_keywords()

                        if exposure_layer.is_vector:
                            exposure_dict['layertype'] = 'vector'
                        else:
                            exposure_dict['layertype'] = 'raster'

                        if hazard_layer.is_vector:
                            hazard_dict['layertype'] = 'vector'
                        else:
                            exposure_dict['layertype'] = 'raster'

                        #get optimal bounding box
                        common_bbox = bbox_intersection(
                            exposure_layer.get_bounding_box(),
                            hazard_layer.get_bounding_box())
                        print exposure_layer.get_bounding_box()
                        print hazard_layer.get_bounding_box()
                        bbox_string = ''
                        try:
                            for val in common_bbox:
                                bbox_string += str(val) + ' '
                        except:
                            writeout = 'The layers have no intersection!'
                        else:
                            #gdal clip
                            dest = 'hazard_tmp.shp'
                            src = hazard_layer.filename
                            print src
                            try:
                                call(
                                    "ogr2ogr -clipsrc %s %s %s" % \
                                    (bbox_string, dest, src), shell=True
                                )
                            except:
                                print 'could not clip hazard'
                            else:
                                print 'created clipped hazard. Reading layer now.'
                            try:
                                clipped_hazard = read_layer("hazard_tmp.shp")
                            except:
                                print 'something went wrong when reading the clipped hazard'
                            else:
                                print clipped_hazard

                            dest = 'exposure_tmp.shp'
                            src = exposure_layer.filename
                            print src
                            try:
                                call(
                                    "ogr2ogr -clipsrc %s %s %s" % \
                                    (bbox_string, dest, src), shell=True
                                )
                            except:
                                print 'could not clip exposure'
                            else:
                                print 'created clipped exposure. Reading layer now.'
                            try:
                                clipped_exposure = read_layer(
                                    "exposure_tmp.shp")
                            except:
                                print 'something went wrong when reading the clipped exposure'
                            else:
                                print clipped_exposure
                            #get impact function based on layer keywords
                            fncs = get_admissible_plugins(
                                [hazard_dict, exposure_dict])
                            impact_fnc = fncs.values()[0]

                            layers = [clipped_hazard, clipped_exposure]

                            # Call calculate_impact
                            impact_file = calculate_impact(
                                layers, impact_function)

                            tmpfile = 'tmp%s.json' % impact_name

                            #5. Serialize the output into json and write out
                            # Convert the impact file into a json file
                            call([
                                'ogr2ogr', '-f', 'GeoJSON', tmpfile,
                                impact_file.filename
                            ])
                            # Open the json file
                            f = open(tmpfile)
                            #FIXME: Something needs to be done about the encoding
                            # Load the file as json
                            json_data = json.loads(f.read(), )

                            # Write it out as json
                            writeout = json.dumps(json_data, )

                            #close the file, and delete temporary files
                            f.close()
                            os.remove(tmpfile)
                            os.remove("hazard_tmp.shp")
                            os.remove("exposure_tmp.shp")
                            #os.remove(impact_file.filename)

                            #6. Cache
                            if cache:
                                redis_server.set(impact_name, writeout)
                                #use setex to add a cache expiry
                            #writeout = json.dumps(impact_file.data, encoding='latin-1')
                    else:
                        writeout = 'Sorry, your query returned one or' + \
                            ' more empty matches'


#        except:
#            writeout = 'Something went wrong! Hmmm...'

        self.set_header('Content-Type', 'application/javascript')
        self.write(writeout)
예제 #9
0
                       'be a sequence of the '
                       'form [west, south, east, north]' %
                       (str(x), str(type(x))[1:-1]))
        try:
            list(x)
        except:
            raise InvalidBoundingBoxException(myMessage)
        try:
            verify(len(x) == 4, myMessage)
        except VerificationError, e:
            raise InvalidBoundingBoxException(str(e))

    # .. note:: The bbox_intersection function below assumes that
    #           all inputs are in EPSG:4326
    myOptimalExtent = bbox_intersection(theHazardGeoExtent,
                                        theExposureGeoExtent,
                                        theViewportGeoExtent)
    if myOptimalExtent is None:
        # Bounding boxes did not overlap
        myMessage = tr(
            'Bounding boxes of hazard data, exposure data '
            'and viewport did not overlap, so no computation was '
            'done. Please make sure you pan to where the data is and '
            'that hazard and exposure data overlaps.')
        raise InsufficientOverlapException(myMessage)

    return myOptimalExtent


def getBufferedExtent(theGeoExtent, theCellSize):
    """Grow bounding box with one unit of resolution in each direction.
예제 #10
0
def get_optimal_extent(
        hazard_geo_extent, exposure_geo_extent, viewport_geo_extent=None):
    """A helper function to determine what the optimal extent is.

    Optimal extent should be considered as the intersection between
    the three inputs. The inasafe library will perform various checks
    to ensure that the extent is tenable, includes data from both
    etc.

    This is just a thin wrapper around safe.storage.utilities.bbox_intersection

    Typically the result of this function will be used to clip
    input layers to a common extent before processing.

    :param hazard_geo_extent: An array representing the hazard layer
        extents in the form [xmin, ymin, xmax, ymax]. It is assumed that the
        coordinates are in EPSG:4326 although currently no checks are made to
        enforce this.
    :type hazard_geo_extent: list

    :param exposure_geo_extent: An array representing the exposure layer
        extents in the form [xmin, ymin, xmax, ymax]. It is assumed that the
        coordinates are in EPSG:4326 although currently no checks are made
        to enforce this.
    :type exposure_geo_extent: list

    :param viewport_geo_extent: (optional) An array representing the
        viewport extents in the form [xmin, ymin, xmax, ymax]. It is assumed
        that the coordinates are in EPSG:4326 although currently no checks
        are made to enforce this.

        ..note:: We do minimal checking as the inasafe library takes care of
        it for us.

    :returns: An array containing an extent in the form
        [xmin, ymin, xmax, ymax]
        e.g.::
        [100.03, -1.14, 100.81, -0.73]
    :rtype: list

    :raises: Any exceptions raised by the InaSAFE library will be propagated.
    """
    message = tr(
        'theHazardGeoExtent or theExposureGeoExtent cannot be None.Found: '
        '/ntheHazardGeoExtent: %s /ntheExposureGeoExtent: %s' %
        (hazard_geo_extent, exposure_geo_extent))

    if (hazard_geo_extent is None) or (exposure_geo_extent is None):
        raise BoundingBoxError(message)

    # .. note:: The bbox_intersection function below assumes that
    #           all inputs are in EPSG:4326
    optimal_extent = bbox_intersection(
        hazard_geo_extent, exposure_geo_extent, viewport_geo_extent)

    if optimal_extent is None:
        # Bounding boxes did not overlap
        message = tr(
            'Bounding boxes of hazard data, exposure data and viewport did '
            'not overlap, so no computation was done. Please make sure you '
            'pan to where the data is and that hazard and exposure data '
            'overlaps.')
        raise InsufficientOverlapError(message)

    return optimal_extent
예제 #11
0
    def get(self):
#        try:
        exposure_id = self.get_argument('e')
        hazard_id = self.get_argument('h')
        impact_name = 'impact-e%s-h%s' % (exposure_id, hazard_id)
        if exposure_id and hazard_id:
            # First check if the impact already exists in the cache
            try: # try to connect to the redis cache
                redis_server = redis.Redis()
                cache = True
                print 'Successfully connected to redis!'
            except:
                # This is just a flag that will be used later on
                print "I couldn't connect to redis"
                cache = False
            else:
                # If the impact exists, get it from the cache and return
                if redis_server.exists(impact_name):
                    print 'Entry exists in cache!'
                    writeout = redis_server.get(impact_name)
                    self.set_header('Content-Type', 'application/javascript')
                    self.write(writeout)
                    return
                    
            # Query the db and calculate if it doesn't
            try: #try connecting to the pg database
                conn = psycopg2.connect(
                    "dbname='dev' user='******' password='******'"
                )
                print 'Successfully connected to postgres!'
            except:
                writeout = 'Could not connect to the database!'
            else:
                # create a cursor
                cursor = conn.cursor(
                    cursor_factory = psycopg2.extras.DictCursor
                )
                try:
                    #1. Query the db for the layers
                    query = 'SELECT shapefile FROM layers'+\
                                ' WHERE id = %s' % exposure_id
                    cursor.execute(query)
                    exposure = cursor.fetchone()
                    query = 'SELECT shapefile FROM layers'+\
                                ' WHERE id = %s' % hazard_id
                    cursor.execute(query)
                    hazard = cursor.fetchone()
                except:
                    writeout = 'There was something wrong with your query'
                    conn.rollback()
                else:
                    if exposure and hazard:
                        # Pass the shapefile (paths) to read_layer
                        try:
                            exposure_layer = read_layer(exposure['shapefile'])
                            hazard_layer = read_layer(hazard['shapefile'])
                        except:
                            writeout = 'Something went wrong when reading the layers'
                        # Keywords
                        exposure_dict = exposure_layer.get_keywords()
                        hazard_dict = hazard_layer.get_keywords()
                        
                        if exposure_layer.is_vector:
                            exposure_dict['layertype'] = 'vector'
                        else:
                            exposure_dict['layertype'] = 'raster'
                            
                        if hazard_layer.is_vector:
                            hazard_dict['layertype'] = 'vector'
                        else:
                            exposure_dict['layertype'] = 'raster'

                        #get optimal bounding box
                        common_bbox = bbox_intersection(
                            exposure_layer.get_bounding_box(),
                            hazard_layer.get_bounding_box()
                        )
                        print exposure_layer.get_bounding_box()
                        print hazard_layer.get_bounding_box()
                        bbox_string = ''
                        try:
                            for val in common_bbox:
                                bbox_string += str(val) + ' '
                        except:
                            writeout = 'The layers have no intersection!'
                        else:
                            #gdal clip
                            dest = 'hazard_tmp.shp'
                            src = hazard_layer.filename
                            print src
                            try:
                                call(
                                    "ogr2ogr -clipsrc %s %s %s" % \
                                    (bbox_string, dest, src), shell=True
                                )
                            except:
                                print 'could not clip hazard'
                            else:
                                print 'created clipped hazard. Reading layer now.'
                            try:
                                clipped_hazard = read_layer("hazard_tmp.shp")
                            except:
                                print 'something went wrong when reading the clipped hazard'
                            else:
                                print clipped_hazard
                            
                            dest = 'exposure_tmp.shp'
                            src = exposure_layer.filename
                            print src
                            try:
                                call(
                                    "ogr2ogr -clipsrc %s %s %s" % \
                                    (bbox_string, dest, src), shell=True
                                )
                            except:
                                print 'could not clip exposure'
                            else:
                                print 'created clipped exposure. Reading layer now.'
                            try:
                                clipped_exposure = read_layer("exposure_tmp.shp")
                            except:
                                print 'something went wrong when reading the clipped exposure'
                            else:
                                print clipped_exposure
                            #get impact function based on layer keywords
                            fncs = get_admissible_plugins([hazard_dict, 
                                exposure_dict])
                            impact_fnc = fncs.values()[0]

                            layers = [clipped_hazard, clipped_exposure]

                            # Call calculate_impact
                            impact_file = calculate_impact(
                                layers, impact_function
                            )
                            
                            tmpfile = 'tmp%s.json' % impact_name

                            #5. Serialize the output into json and write out
                            # Convert the impact file into a json file
                            call(['ogr2ogr', '-f', 'GeoJSON', tmpfile, 
                                    impact_file.filename])
                            # Open the json file        
                            f = open(tmpfile)
                            #FIXME: Something needs to be done about the encoding
                            # Load the file as json
                            json_data = json.loads(
                                f.read(), 
                            )
                            
                            # Write it out as json
                            writeout = json.dumps(
                                json_data, 
                            )

                            #close the file, and delete temporary files
                            f.close()
                            os.remove(tmpfile)
                            os.remove("hazard_tmp.shp")
                            os.remove("exposure_tmp.shp")
                            #os.remove(impact_file.filename)
                            
                            #6. Cache
                            if cache:
                                redis_server.set(impact_name, writeout)
                                #use setex to add a cache expiry
                            #writeout = json.dumps(impact_file.data, encoding='latin-1')
                    else:
                        writeout = 'Sorry, your query returned one or' + \
                            ' more empty matches'
#        except:
#            writeout = 'Something went wrong! Hmmm...'
    
        self.set_header('Content-Type', 'application/javascript')
        self.write(writeout)
예제 #12
0
def get_optimal_extent(hazard_geo_extent,
                       exposure_geo_extent,
                       viewport_geo_extent=None):
    """A helper function to determine what the optimal extent is.

    Optimal extent should be considered as the intersection between
    the three inputs. The inasafe library will perform various checks
    to ensure that the extent is tenable, includes data from both
    etc.

    This is just a thin wrapper around safe.storage.utilities.bbox_intersection

    Typically the result of this function will be used to clip
    input layers to a common extent before processing.

    :param hazard_geo_extent: An array representing the hazard layer
        extents in the form [xmin, ymin, xmax, ymax]. It is assumed that the
        coordinates are in EPSG:4326 although currently no checks are made to
        enforce this.
    :type hazard_geo_extent: list

    :param exposure_geo_extent: An array representing the exposure layer
        extents in the form [xmin, ymin, xmax, ymax]. It is assumed that the
        coordinates are in EPSG:4326 although currently no checks are made
        to enforce this.
    :type exposure_geo_extent: list

    :param viewport_geo_extent: (optional) An array representing the
        viewport extents in the form [xmin, ymin, xmax, ymax]. It is assumed
        that the coordinates are in EPSG:4326 although currently no checks
        are made to enforce this.

        ..note:: We do minimal checking as the inasafe library takes care of
        it for us.

    :returns: An array containing an extent in the form
        [xmin, ymin, xmax, ymax]
        e.g.::
        [100.03, -1.14, 100.81, -0.73]
    :rtype: list

    :raises: Any exceptions raised by the InaSAFE library will be propagated.
    """
    message = tr(
        'theHazardGeoExtent or theExposureGeoExtent cannot be None.Found: '
        '/ntheHazardGeoExtent: %s /ntheExposureGeoExtent: %s' %
        (hazard_geo_extent, exposure_geo_extent))

    if (hazard_geo_extent is None) or (exposure_geo_extent is None):
        raise BoundingBoxError(message)

    # .. note:: The bbox_intersection function below assumes that
    #           all inputs are in EPSG:4326
    optimal_extent = bbox_intersection(hazard_geo_extent, exposure_geo_extent,
                                       viewport_geo_extent)

    if optimal_extent is None:
        # Bounding boxes did not overlap
        message = tr(
            'Bounding boxes of hazard data, exposure data and viewport did '
            'not overlap, so no computation was done. Please make sure you '
            'pan to where the data is and that hazard and exposure data '
            'overlaps.')
        raise InsufficientOverlapError(message)

    return optimal_extent