Пример #1
0
    def __new__(self, coordinates=None):
        if coordinates is None:
            # empty geometry
            # TODO better way?
            return shapely.from_wkt("LINEARRING EMPTY")
        elif isinstance(coordinates, LineString):
            if type(coordinates) == LinearRing:
                # return original objects since geometries are immutable
                return coordinates
            elif not coordinates.is_valid:
                raise TopologicalError("An input LineString must be valid.")
            else:
                # LineString
                # TODO convert LineString to LinearRing more directly?
                coordinates = coordinates.coords

        else:
            # check coordinates on points
            def _coords(o):
                if isinstance(o, Point):
                    return o.coords[0]
                else:
                    return o

            coordinates = [_coords(o) for o in coordinates]

        if len(coordinates) == 0:
            # empty geometry
            # TODO better constructor + should shapely.linearrings handle this?
            return shapely.from_wkt("LINEARRING EMPTY")

        geom = shapely.linearrings(coordinates)
        if not isinstance(geom, LinearRing):
            raise ValueError("Invalid values passed to LinearRing constructor")
        return geom
Пример #2
0
def _repair(geom):
    repaired = geom.buffer(0) if geom.geom_type in ["Polygon", "MultiPolygon"] else geom
    if repaired.is_valid:
        return repaired
    else:
        raise TopologicalError(
            "geometry is invalid (%s) and cannot be repaired" % explain_validity(repaired)
        )
Пример #3
0
def _reproject_geom(geometry, src_crs, dst_crs, validity_check=True):
    if geometry.is_empty or src_crs == dst_crs:
        return geometry.buffer(0)
    out_geom = to_shape(
        transform_geom(src_crs.to_dict(), dst_crs.to_dict(),
                       mapping(geometry))).buffer(0)
    if validity_check and not out_geom.is_valid or out_geom.is_empty:
        raise TopologicalError("invalid geometry after reprojection")
    return out_geom
Пример #4
0
 def width_height(bounds):
     try:
         l, b, r, t = reproject_geometry(box(*bounds),
                                         src_crs=tile.crs,
                                         dst_crs=dst_pyramid.crs).bounds
     except ValueError:
         raise TopologicalError(
             "bounds cannot be translated into target CRS")
     return r - l, t - b
Пример #5
0
 def _reproject_geom(geometry, src_crs, dst_crs):
     if geometry.is_empty or src_crs == dst_crs:
         return _repair(geometry)
     out_geom = _repair(
         to_shape(
             transform_geom(src_crs.to_dict(), dst_crs.to_dict(),
                            mapping(geometry))))
     if validity_check and (not out_geom.is_valid or out_geom.is_empty):
         raise TopologicalError("invalid geometry after reprojection")
     return out_geom
Пример #6
0
    def __new__(self, coordinates=None):
        """
        Parameters
        ----------
        coordinates : sequence
            A sequence of (x, y [,z]) numeric coordinate pairs or triples.
            Also can be a sequence of Point objects.

        Rings are implicitly closed. There is no need to specific a final
        coordinate pair identical to the first.

        Example
        -------
        Construct a square ring.

          >>> ring = LinearRing( ((0, 0), (0, 1), (1 ,1 ), (1 , 0)) )
          >>> ring.is_closed
          True
          >>> ring.length
          4.0
        """
        if coordinates is None:
            # empty geometry
            # TODO better way?
            return shapely.from_wkt("LINEARRING EMPTY")
        elif isinstance(coordinates, LineString):
            if type(coordinates) == LinearRing:
                # return original objects since geometries are immutable
                return coordinates
            elif not coordinates.is_valid:
                raise TopologicalError("An input LineString must be valid.")
            else:
                # LineString
                # TODO convert LineString to LinearRing more directly?
                coordinates = coordinates.coords

        else:
            # check coordinates on points
            def _coords(o):
                if isinstance(o, Point):
                    return o.coords[0]
                else:
                    return o

            coordinates = [_coords(o) for o in coordinates]

        if len(coordinates) == 0:
            # empty geometry
            # TODO better constructor + should shapely.linearrings handle this?
            return shapely.from_wkt("LINEARRING EMPTY")

        geom = shapely.linearrings(coordinates)
        if not isinstance(geom, LinearRing):
            raise ValueError("Invalid values passed to LinearRing constructor")
        return geom
Пример #7
0
def _get_reprojected_features(input_file=None,
                              dst_bounds=None,
                              dst_crs=None,
                              validity_check=False):
    with fiona.open(input_file, 'r') as vector:
        vector_crs = CRS(vector.crs)
        # Reproject tile bounding box to source file CRS for filter:
        if vector_crs == dst_crs:
            dst_bbox = box(*dst_bounds)
        else:
            dst_bbox = reproject_geometry(box(*dst_bounds),
                                          src_crs=dst_crs,
                                          dst_crs=vector_crs,
                                          validity_check=True)
        for feature in vector.filter(bbox=dst_bbox.bounds):
            feature_geom = to_shape(feature['geometry'])
            if not feature_geom.is_valid:
                feature_geom = feature_geom.buffer(0)
                # skip feature if geometry cannot be repaired
                if not feature_geom.is_valid:
                    logger.exception("feature omitted: %s",
                                     explain_validity(feature_geom))
                    continue
            # only return feature if geometry type stayed the same after
            # reprojecction
            geom = clean_geometry_type(feature_geom.intersection(dst_bbox),
                                       feature_geom.geom_type)
            if geom:
                # Reproject each feature to tile CRS
                try:
                    geom = reproject_geometry(geom,
                                              src_crs=vector_crs,
                                              dst_crs=dst_crs,
                                              validity_check=validity_check)
                    if validity_check and not geom.is_valid:
                        raise TopologicalError(
                            "reprojected geometry invalid: %s" %
                            (explain_validity(geom)))
                except TopologicalError:
                    logger.exception("feature omitted: reprojection failed")
                yield {
                    'properties': feature['properties'],
                    'geometry': mapping(geom)
                }
            else:
                logger.exception(
                    "feature omitted: geometry type changed after reprojection"
                )
Пример #8
0
def polylabel(polygon, tolerance=1.0):
    """Finds pole of inaccessibility for a given polygon. Based on
    Vladimir Agafonkin's https://github.com/mapbox/polylabel
    Parameters
    ----------
    polygon : shapely.geometry.Polygon
    tolerance : int or float, optional
                `tolerance` represents the highest resolution in units of the
                input geometry that will be considered for a solution. (default
                value is 1.0).
    Returns
    -------
    shapely.geometry.Point
        A point representing the pole of inaccessibility for the given input
        polygon.
    Raises
    ------
    shapely.geos.TopologicalError
        If the input polygon is not a valid geometry.
    Example
    -------
    >>> polygon = LineString([(0, 0), (50, 200), (100, 100), (20, 50),
    ... (-100, -20), (-150, -200)]).buffer(100)
    >>> label = polylabel(polygon, tolerance=10)
    >>> label.wkt
    'POINT (59.35615556364569 121.8391962974644)'
    """
    if not polygon.is_valid:
        raise TopologicalError('Invalid polygon')
    minx, miny, maxx, maxy = polygon.bounds
    width = maxx - minx
    height = maxy - miny
    cell_size = min(width, height)
    h = cell_size / 2.0
    cell_queue = []

    # First best cell approximation is one constructed from the centroid
    # of the polygon
    x, y = polygon.centroid.coords[0]
    best_cell = Cell(x, y, 0, polygon)

    # Special case for rectangular polygons avoiding floating point error
    bbox_cell = Cell(minx + width / 2.0, miny + height / 2, 0, polygon)
    if bbox_cell.distance > best_cell.distance:
        best_cell = bbox_cell

    # build a regular square grid covering the polygon
    x = minx
    while x < maxx:
        y = miny
        while y < maxy:
            heappush(cell_queue, Cell(x + h, y + h, h, polygon))
            y += cell_size
        x += cell_size

    # minimum priority queue
    while cell_queue:
        cell = heappop(cell_queue)

        # update the best cell if we find a better one
        if cell.distance > best_cell.distance:
            best_cell = cell

        # continue to the next iteration if we cant find a better solution
        # based on tolerance
        if cell.max_distance - best_cell.distance <= tolerance:
            continue

        # split the cell into quadrants
        h = cell.h / 2.0
        heappush(cell_queue, Cell(cell.x - h, cell.y - h, h, polygon))
        heappush(cell_queue, Cell(cell.x + h, cell.y - h, h, polygon))
        heappush(cell_queue, Cell(cell.x - h, cell.y + h, h, polygon))
        heappush(cell_queue, Cell(cell.x + h, cell.y + h, h, polygon))

    return best_cell.centroid
Пример #9
0
def geos_linearring_from_py(ob, update_geom=None, update_ndim=0):
    # If a LinearRing is passed in, clone it and return
    # If a valid LineString is passed in, clone the coord seq and return a
    # LinearRing.
    #
    # NB: access to coordinates using the array protocol has been moved
    # entirely to the speedups module.

    if isinstance(ob, LineString):
        if type(ob) == LinearRing:
            return geos_geom_from_py(ob)
        elif not ob.is_valid:
            raise TopologicalError("An input LineString must be valid.")
        elif ob.is_closed and len(ob.coords) >= 4:
            return geos_geom_from_py(ob, lgeos.GEOSGeom_createLinearRing)
        else:
            ob = list(ob.coords)

    try:
        m = len(ob)
    except TypeError:  # generators
        ob = list(ob)
        m = len(ob)

    if m == 0:
        return None

    def _coords(o):
        if isinstance(o, Point):
            return o.coords[0]
        else:
            return o

    n = len(_coords(ob[0]))
    if m < 3:
        raise ValueError("A LinearRing must have at least 3 coordinate tuples")
    assert (n == 2 or n == 3)

    # Add closing coordinates if not provided
    if (m == 3 or _coords(ob[0])[0] != _coords(ob[-1])[0]
            or _coords(ob[0])[1] != _coords(ob[-1])[1]):
        M = m + 1
    else:
        M = m

    # Create a coordinate sequence
    if update_geom is not None:
        if n != update_ndim:
            raise ValueError(
                "Coordinate dimensions mismatch: target geom has {} dims, "
                "update geom has {} dims".format(n, update_ndim))
        cs = lgeos.GEOSGeom_getCoordSeq(update_geom)
    else:
        cs = lgeos.GEOSCoordSeq_create(M, n)

    # add to coordinate sequence
    for i in range(m):
        coords = _coords(ob[i])
        # Because of a bug in the GEOS C API,
        # always set X before Y
        lgeos.GEOSCoordSeq_setX(cs, i, coords[0])
        lgeos.GEOSCoordSeq_setY(cs, i, coords[1])
        if n == 3:
            try:
                lgeos.GEOSCoordSeq_setZ(cs, i, coords[2])
            except IndexError:
                raise ValueError("Inconsistent coordinate dimensionality")

    # Add closing coordinates to sequence?
    if M > m:
        coords = _coords(ob[0])
        # Because of a bug in the GEOS C API,
        # always set X before Y
        lgeos.GEOSCoordSeq_setX(cs, M - 1, coords[0])
        lgeos.GEOSCoordSeq_setY(cs, M - 1, coords[1])
        if n == 3:
            lgeos.GEOSCoordSeq_setZ(cs, M - 1, coords[2])

    if update_geom is not None:
        return None
    else:
        return lgeos.GEOSGeom_createLinearRing(cs), n
Пример #10
0
def tile_to_zoom_level(tile,
                       dst_pyramid=None,
                       matching_method="gdal",
                       precision=8):
    """
    Determine the best zoom level in target TilePyramid from given Tile.

    Parameters
    ----------
    tile : BufferedTile
    dst_pyramid : BufferedTilePyramid
    matching_method : str ('gdal' or 'min')
        gdal: Uses GDAL's standard method. Here, the target resolution is calculated by
            averaging the extent's pixel sizes over both x and y axes. This approach
            returns a zoom level which may not have the best quality but will speed up
            reading significantly.
        min: Returns the zoom level which matches the minimum resolution of the extent's
            four corner pixels. This approach returns the zoom level with the best
            possible quality but with low performance. If the tile extent is outside of
            the destination pyramid, a TopologicalError will be raised.
    precision : int
        Round resolutions to n digits before comparing.

    Returns
    -------
    zoom : int
    """
    def width_height(bounds):
        try:
            l, b, r, t = reproject_geometry(box(*bounds),
                                            src_crs=tile.crs,
                                            dst_crs=dst_pyramid.crs).bounds
        except ValueError:
            raise TopologicalError(
                "bounds cannot be translated into target CRS")
        return r - l, t - b

    if tile.tp.crs == dst_pyramid.crs:
        return tile.zoom
    else:
        if matching_method == "gdal":
            # use rasterio/GDAL method to calculate default warp target properties
            # enabling CHECK_WITH_INVERT_PROJ fixes #269, otherwise this function would
            # return a non-optimal zooom level for reprojection
            with rasterio.Env(CHECK_WITH_INVERT_PROJ=True):
                transform, width, height = calculate_default_transform(
                    tile.tp.crs, dst_pyramid.crs, tile.width, tile.height,
                    *tile.bounds)
                # this is the resolution the tile would have in destination CRS
                tile_resolution = round(transform[0], precision)
        elif matching_method == "min":
            # calculate the minimum pixel size from the four tile corner pixels
            l, b, r, t = tile.bounds
            x = tile.pixel_x_size
            y = tile.pixel_y_size
            res = []
            for bounds in [
                (l, t - y, l + x, t),  # left top
                (l, b, l + x, b + y),  # left bottom
                (r - x, b, r, b + y),  # right bottom
                (r - x, t - y, r, t)  # right top
            ]:
                try:
                    w, h = width_height(bounds)
                    res.extend([w, h])
                except TopologicalError:
                    logger.debug("pixel outside of destination pyramid")
            if res:
                tile_resolution = round(min(res), precision)
            else:
                raise TopologicalError("tile outside of destination pyramid")
        else:
            raise ValueError("invalid method given: %s", matching_method)
        logger.debug(
            "we are looking for a zoom level interpolating to %s resolution",
            tile_resolution)
        zoom = 0
        while True:
            td_resolution = round(dst_pyramid.pixel_x_size(zoom), precision)
            if td_resolution <= tile_resolution:
                break
            zoom += 1
        logger.debug("target zoom for %s: %s (%s)", tile_resolution, zoom,
                     td_resolution)
        return zoom