def get_cropped_recursively(meter_x: float, meter_y: float, zoom: int, path: str, steps: int, do_epx_scale: bool,
                            file_ending: str):
    """Recursively crops tiles until the required one has been generated.

    To prevent a stack overflow, the steps are limited to MAX_STEP_NUMBER.
    """

    if steps >= MAX_STEP_NUMBER:
        # No tile that could be cropped has been found for the set MAX_STEP_NUMBER
        logger.error("{}: No tile could be found or created (tried from zoom {} down to {}) at location {}, {}!"
                     " Possible causes: Missing data, wrong path, no write permissions"
                     .format(path, zoom + steps, zoom, meter_x, meter_y))
        return None

    full_path = utils.join_path(path, FULL_PATH)

    this_point = webmercator.Point(meter_x=meter_x, meter_y=meter_y, zoom_level=zoom)
    this_point_filename = full_path.format(zoom, this_point.tile_x, this_point.tile_y, file_ending)

    if not os.path.isfile(this_point_filename) or (os.path.getsize(this_point_filename) == 0):
        prev_point = webmercator.Point(meter_x=meter_x, meter_y=meter_y, zoom_level=zoom - 1)
        prev_point_filename = full_path.format(zoom, prev_point.tile_x, prev_point.tile_y, file_ending)

        if not os.path.isfile(prev_point_filename) or (os.path.getsize(prev_point_filename) == 0):
            get_cropped_recursively(meter_x, meter_y, zoom - 1, path, steps + 1, do_epx_scale, file_ending)

        get_cropped_for_next_tile(meter_x, meter_y, zoom - 1, path, do_epx_scale, file_ending)

    return this_point_filename
def fetch_tiles(scenario_id,
                tile_url=TILE_URL_FORMAT,
                layer=DEFAULT_LAYER,
                zoom_from=DEFAULT_ZOOM_FROM,
                zoom_to=DEFAULT_ZOOM_TO):

    logger.info("fetch layer {} from {} (z: {} to {})".format(
        layer, tile_url, zoom_from, zoom_to))

    # fetch possible extent from scenario
    scenario = Scenario.objects.get(id=scenario_id)
    bounding_box = scenario.bounding_polygon.envelope
    logger.debug(bounding_box)

    min_x, min_y = bounding_box.centroid
    max_x, max_y = min_x, min_y
    for x, y in bounding_box.coords[
            0]:  # first of multiple geometries hold the bb
        if x < min_x:
            min_x = x
        if x > max_x:
            max_x = x
        if y < min_y:
            min_y = y
        if y > max_y:
            max_y = y

    # get all tiles in the available extent and zoomlevel
    for zoom in range(zoom_from, zoom_to):
        p_from = webmercator.Point(meter_x=min_x,
                                   meter_y=min_y,
                                   zoom_level=zoom)
        p_to = webmercator.Point(meter_x=max_x, meter_y=max_y, zoom_level=zoom)

        logger.info(
            "getting all tiles with z {} for ({}, {}), ({}, {})".format(
                zoom, p_from.tile_x, p_from.tile_y, p_to.tile_x, p_to.tile_y))
        for y in range(p_to.tile_y, p_from.tile_y + 1):
            for x in range(p_from.tile_x, p_to.tile_x + 1):
                fetch_tile(tile_url, layer, x, y, zoom)
Exemple #3
0
def get_lines_for_tile(request, line_type_id, tile_x, tile_y, zoom):
    ret = {}

    line_type_id = int(line_type_id)

    # Construct the polygon which represents this tile, filter assets with that polygon
    point = webmercator.Point(meter_x=float(tile_x),
                              meter_y=float(tile_y),
                              zoom_level=int(zoom))
    tile_center = webmercator.Point(tile_x=point.tile_x,
                                    tile_y=point.tile_y,
                                    zoom_level=int(zoom))

    polygon = Polygon(
        ((tile_center.meter_x + tile_center.meters_per_tile / 2,
          tile_center.meter_y + tile_center.meters_per_tile / 2),
         (tile_center.meter_x + tile_center.meters_per_tile / 2,
          tile_center.meter_y - tile_center.meters_per_tile / 2),
         (tile_center.meter_x - tile_center.meters_per_tile / 2,
          tile_center.meter_y - tile_center.meters_per_tile / 2),
         (tile_center.meter_x - tile_center.meters_per_tile / 2,
          tile_center.meter_y + tile_center.meters_per_tile / 2),
         (tile_center.meter_x + tile_center.meters_per_tile / 2,
          tile_center.meter_y + tile_center.meters_per_tile / 2)),
        srid=3857)

    segments = LineSegment.objects.filter(type=line_type_id,
                                          line__intersects=polygon)

    # Add the segments to the response
    for segment in segments.all():
        # Ensure WebMercator coordinates
        line = segment.line
        line.transform(3857)

        ret[segment.id] = {"line": line.coords, "width": segment.width}

    return JsonResponse(ret)
def get_assetpositions(request, zoom, tile_x, tile_y, assettype_id):
    ret = {"assets": {}}

    # Construct the polygon which represents this tile, filter assets with that polygon
    point = webmercator.Point(meter_x=float(tile_x),
                              meter_y=float(tile_y),
                              zoom_level=int(zoom))
    tile_center = webmercator.Point(tile_x=point.tile_x,
                                    tile_y=point.tile_y,
                                    zoom_level=int(zoom))

    polygon = Polygon(
        ((tile_center.meter_x + tile_center.meters_per_tile / 2,
          tile_center.meter_y + tile_center.meters_per_tile / 2),
         (tile_center.meter_x + tile_center.meters_per_tile / 2,
          tile_center.meter_y - tile_center.meters_per_tile / 2),
         (tile_center.meter_x - tile_center.meters_per_tile / 2,
          tile_center.meter_y - tile_center.meters_per_tile / 2),
         (tile_center.meter_x - tile_center.meters_per_tile / 2,
          tile_center.meter_y + tile_center.meters_per_tile / 2),
         (tile_center.meter_x + tile_center.meters_per_tile / 2,
          tile_center.meter_y + tile_center.meters_per_tile / 2)))

    assets = AssetPositions.objects.filter(
        asset_type=AssetType.objects.get(id=assettype_id),
        location__contained=polygon).all()

    ret["assets"] = {
        asset.id: {
            "position": [asset.location.x, asset.location.y],
            "modelpath": asset.asset.name
        }
        for asset in assets
    }

    return JsonResponse(ret, safe=False)
def generate_dhm_db(x: int, y: int, zoom: int):

    # create the empty dhm array for this tile with an unsigned integer with 20 bit precision
    np_heightmap = np.zeros((TILE_SIZE_PIXEL, TILE_SIZE_PIXEL), dtype=np.uint32)

    # iterate to all pixels of the image in terms of projected coordinates
    point = webmercator.Point(tile_x=x, tile_y=y, zoom_level=zoom)
    meters_x_steps = np.arange(point.meter_x, point.meters_per_tile + point.meter_x, point.meters_per_pixel)
    meters_y_steps = np.arange(point.meter_y, point.meters_per_tile + point.meter_y, point.meters_per_pixel)
    pixel_x, pixel_y = -1, -1
    for m_x_from, m_x_to in utils.lookahead(meters_x_steps):
        pixel_x += 1
        for m_y_from, m_y_to in utils.lookahead(meters_y_steps):
            pixel_y += 1

            # don't calculate if we are beyond the last pixel
            if m_x_to is None or m_y_to is None:
                continue

            # get the available height information of the requested pixel
            bbox = Polygon.from_bbox((m_x_from, m_y_from, m_x_to, m_y_to))
            dhm_points = DigitalHeightModel.objects.filter(poly__contained=bbox)

            # if there are registered heights we have to calculate the average
            if dhm_points:
                np_heightmap[pixel_x, pixel_y] = dhm_points.aggregate(Avg('height'))
            # FIXME: we have to somehow estimate from the surounding values
            else:
                pass

    # store the result as cache of the tile in the database
    tile = get_or_initialize_tile(x, y, zoom)
    tile.heightmap = np_heightmap
    tile.save()

    return np_heightmap
def fetch_wmts_tiles(bounding_box: Polygon,
                     url=DEFAULT_URL,
                     layer=DEFAULT_LAYER,
                     zoom_from=DEFAULT_ZOOM_FROM,
                     zoom_to=DEFAULT_ZOOM_TO):

    # initialize wmts connection
    logger.info("fetch layer {} from {} (z: {} to {})".format(
        layer, url, zoom_from, zoom_to))
    tile_server = wmts.WebMapTileService(url)

    # calculate possible extent
    if tile_server.contents[layer]:
        long1, lat1, long2, lat2 = tile_server.contents[layer].boundingBoxWGS84
        p1 = webmercator.Point(latitude=lat1, longitude=long1)
        p2 = webmercator.Point(latitude=lat2, longitude=long2)
        server_box = Polygon(
            ((p1.meter_x, p1.meter_y), (p1.meter_x, p2.meter_y),
             (p2.meter_x, p2.meter_y), (p2.meter_x, p1.meter_y), (p1.meter_x,
                                                                  p1.meter_y)))
        unified_bb = server_box
        if bounding_box is not None:
            unified_bb = server_box.intersection(bounding_box)

        min_x, min_y = unified_bb.centroid
        max_x, max_y = min_x, min_y
        for x, y in unified_bb.coords[
                0]:  # first of multiple geometries hold the bb
            if x < min_x:
                min_x = x
            if x > max_x:
                max_x = x
            if y < min_y:
                min_y = y
            if y > max_y:
                max_y = y

        # get all tiles in the available extent and zoomlevel
        for zoom in range(zoom_from,
                          zoom_to):  # FIXME: fixed values -> configurable
            p_from = webmercator.Point(meter_x=min_x,
                                       meter_y=min_y,
                                       zoom_level=zoom)
            p_to = webmercator.Point(meter_x=max_x,
                                     meter_y=max_y,
                                     zoom_level=zoom)

            logger.info(
                "getting all tiles with z {} for ({}, {}), ({}, {})".format(
                    zoom, p_from.tile_x, p_from.tile_y, p_to.tile_x,
                    p_to.tile_y))
            for y in range(p_to.tile_y, p_from.tile_y + 1):
                for x in range(p_from.tile_x, p_to.tile_x + 1):
                    retry = True
                    while retry:
                        try:
                            fetch_wmts_tile(tile_server, layer, x, y, zoom)
                            retry = False
                        except Exception as e:
                            logger.warning(
                                "Got exception {} - need to repeat".format(e))
    else:
        pass  # TODO: error
def get_cropped_for_next_tile(meter_x: float, meter_y: float, zoom: int, path: str, do_epx_scale: bool,
                              file_ending: str):
    """Takes the tile at the given parameters (which must exist!) and crops it to create a tile one zoom level above
    the given one. This new tile is then saved in the LOD pyramid.

    The quarter of the existing tile to crop to is chosen by utilizing how tile coordinates work in OSM:
    2x,2y    2x+1,2y
    2x,2y+1  2x+1,2y+1
    """

    p_wanted = webmercator.Point(meter_x=meter_x, meter_y=meter_y, zoom_level=zoom + 1)
    p_available = webmercator.Point(meter_x=meter_x, meter_y=meter_y, zoom_level=zoom)

    if p_wanted.tile_x % 2 == 0:
        left_right = [0, 0.5]
    else:
        left_right = [0.5, 1]

    if p_wanted.tile_y % 2 == 0:
        upper_lower = [0, 0.5]
    else:
        upper_lower = [0.5, 1]

    zoom_path_template = utils.join_path(path, ZOOM_PATH)
    x_path_template = utils.join_path(path, METER_X_PATH)
    full_path_template = utils.join_path(path, FULL_PATH)

    available_filename = full_path_template.format(zoom, p_available.tile_x, p_available.tile_y, file_ending)
    wanted_filename = full_path_template.format(zoom + 1, p_wanted.tile_x, p_wanted.tile_y, file_ending)

    if not os.path.isfile(available_filename):
        # Nothing here yet - we might recurse further in get_cropped_recursively
        return

    x_path = x_path_template.format(zoom + 1, p_wanted.tile_x)
    os.makedirs(x_path, exist_ok=True)

    try:
        # Wait for access to the file to become available - in case the image is still being written to
        while True:
            try:
                os.rename(available_filename, available_filename)
                break
            except OSError as e:
                pass
    
        available_image = Image.open(available_filename)
    except OSError as error:
        logger.error("OSError while opening image: {}".format(error))
        return
    except Error as error:
        logger.error("Other Error while opening image: {}".format(error))
        return
    
    # PIL needs the image to be in RGB mode for processing - convert it if necessary
    original_image_mode = available_image.mode
    if original_image_mode != "RGB":
        available_image.convert('RGB')

    available_size = tuple(available_image.size)

    # If the available image is smaller than 2x2, this won't work
    if available_size[0] < 2:
        logger.warning("Image {} was too small, not proceeding!".format(available_filename))
        return

    wanted_image = available_image.crop((int(left_right[0] * available_size[0]),
                                         int(upper_lower[0] * available_size[1]),
                                         int(left_right[1] * available_size[0]),
                                         int(upper_lower[1] * available_size[1])))

    if do_epx_scale:
        wanted_image = epx.scale_epx(wanted_image)

    # If the image has been converted to RGB for processing, convert it back to the original mode
    if original_image_mode != wanted_image.mode:
        wanted_image.convert(original_image_mode)

    # It is possible that in the time since we last checked whether the image exists,
    #  the same request was handled in another thread. This means that the image already
    #  exists at this point. This error doesn't matter; in any case, the image exists
    try:
        out_file = open(wanted_filename, 'wb')
    except OSError as error:
        logger.error("OSError: Could not open new image file {}. Got error: {}".format(available_filename, error))
    except IOError as error:
        logger.error("IOError: Could not open new image file {}. Got error: {}".format(available_filename, error))
       
    try:
        wanted_image.save(out_file)
        wanted_image.close()

        # Make sure that the file is completely written and closed - otherwise the client
        #  may try to open an image which is still unfinished
        out_file.flush()
        os.fsync(out_file.fileno())
        out_file.close()

        logger.debug("Done saving image {}".format(wanted_filename))
    except IOError as error:
        logger.warning("IOError: Image {} could not be saved! This could be due to another thread having saved it earlier, "
                       "in which case it is not an issue. Error: {}".format(wanted_filename, error))
    except OSError as error:
        logger.error("OSError: Could not save file {} - this file does not seem valid. Got error: {}".format(available_filename, error))
def get_corresponding_tile_coordinates(location: Point, lod):
    coord = webmercator.Point(meter_x=location.x, meter_y=location.y, zoom_level=lod)
    return coord.tile_x, coord.tile_y