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)
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