Exemplo n.º 1
0
def _parse_stg_entries_for_convex_hull(stg_entries: List[STGEntry],
                                       my_coord_transformation: Transformation,
                                       tile_box: shg.Polygon) -> None:
    """
    Parses the ac-file content for a set of STGEntry objects and sets their boundary attribute
    to be the convex hull of all points in the ac-file in the specified local coordinate system.
    If there is a problem creating the convex hull, then the stg_entry will be removed.
    """
    ac_filename = ""
    for entry in reversed(stg_entries):
        if entry.verb_type in [
                STGVerbType.object_static, STGVerbType.object_shared
        ]:
            try:
                ac_filename = entry.obj_filename
                if ac_filename.endswith(".xml"):
                    entry.overwrite_filename(
                        _extract_ac_from_xml(
                            entry.get_obj_path_and_name(),
                            entry.get_obj_path_and_name(
                                parameters.PATH_TO_SCENERY)))
                boundary_polygon = _extract_boundary(
                    entry.get_obj_path_and_name(),
                    entry.get_obj_path_and_name(parameters.PATH_TO_SCENERY))
                rotated_polygon = affinity.rotate(boundary_polygon,
                                                  entry.hdg - 90, (0, 0))
                x_y_point = my_coord_transformation.to_local(
                    (entry.lon, entry.lat))
                translated_polygon = affinity.translate(
                    rotated_polygon, x_y_point[0], x_y_point[1])
                if entry.verb_type is STGVerbType.object_static and parameters.OVERLAP_CHECK_CH_BUFFER_STATIC > 0.01:
                    entry.convex_hull = translated_polygon.buffer(
                        parameters.OVERLAP_CHECK_CH_BUFFER_STATIC,
                        shg.CAP_STYLE.square)
                elif entry.verb_type is STGVerbType.object_shared and parameters.OVERLAP_CHECK_CH_BUFFER_SHARED > 0.01:
                    entry.convex_hull = translated_polygon.buffer(
                        parameters.OVERLAP_CHECK_CH_BUFFER_SHARED,
                        shg.CAP_STYLE.square)
                else:
                    entry.convex_hull = translated_polygon
            except IOError as reason:
                logging.warning(
                    "Ignoring unreadable stg_entry of type %s and file name %s: %s",
                    entry.verb_type, entry.obj_filename, reason)
            except ValueError:
                # Happens e.g. for waterfalls, where the xml-file only references a <particlesystem>
                logging.debug(
                    "AC-filename could be wrong in xml-file %s - or just no ac-file referenced",
                    ac_filename)

            # Now check, whether we are interested
            if entry.convex_hull is None or entry.convex_hull.is_valid is False or entry.convex_hull.is_empty:
                logging.warning(
                    "Ignoring stg_entry of type %s and file name %s: convex hull invalid",
                    entry.verb_type, entry.obj_filename)
                stg_entries.remove(entry)
            elif entry.convex_hull.within(
                    tile_box) is False and entry.convex_hull.disjoint(
                        tile_box):
                stg_entries.remove(entry)
Exemplo n.º 2
0
 def tree_from_node(cls, node: op.Node, coords_transform: co.Transformation,
                    fg_elev: utilities.FGElev) -> 'Tree':
     elev = fg_elev.probe_elev((node.lon, node.lat), True)
     x, y = coords_transform.to_local((node.lon, node.lat))
     tree = Tree(node.osm_id, x, y, elev)
     tree.parse_tags(node.tags)
     return tree
Exemplo n.º 3
0
def get_extent_local(
        transformer: co.Transformation) -> typing.Tuple[co.Vec2d, co.Vec2d]:
    cmin = co.Vec2d(BOUNDARY_WEST, BOUNDARY_SOUTH)
    cmax = co.Vec2d(BOUNDARY_EAST, BOUNDARY_NORTH)
    logging.info("min/max " + str(cmin) + " " + str(cmax))
    lmin = co.Vec2d(transformer.to_local((cmin.x, cmin.y)))
    lmax = co.Vec2d(transformer.to_local((cmax.x, cmax.y)))
    return lmin, lmax
Exemplo n.º 4
0
def _get_tile_bounds_local(
        transform: co.Transformation) -> Tuple[float, float, float, float]:
    """Based on parameters use the tile border as bounds.
    To be used as axis limits.
    """
    min_point = transform.to_local((p.BOUNDARY_WEST, p.BOUNDARY_SOUTH))
    max_point = transform.to_local((p.BOUNDARY_EAST, p.BOUNDARY_NORTH))
    return min_point[0], min_point[1], max_point[0], max_point[1]
Exemplo n.º 5
0
def refs_to_ring(coords_transform: Transformation, refs: List[int],
                 nodes_dict: Dict[int, Node]) -> shg.LinearRing:
    """Accept a list of OSM refs, return a linear ring."""
    coords = []
    for ref in refs:
        c = nodes_dict[ref]
        coords.append(coords_transform.to_local((c.lon, c.lat)))

    ring = shg.polygon.LinearRing(coords)
    return ring
Exemplo n.º 6
0
 def create_polygons(self, transformer: co.Transformation) -> Optional[List[Polygon]]:
     if self.not_empty:
         boundaries = list()
         for my_list in self.nodes_lists:
             if len(my_list) < 3:
                 continue
             my_boundary = Polygon([transformer.to_local(n) for n in my_list])
             if my_boundary.is_valid:
                 boundaries.append(my_boundary)
         return boundaries
     return None
Exemplo n.º 7
0
def read_btg_file(transformer: Transformation, airport_code: Optional[str] = None) -> Optional[BTGReader]:
    """There is a need to do a local coordinate transformation, as BTG also has a local coordinate
    transformation, but there the center will be in the middle of the tile, whereas here it can be
     another place if the boundary is not a whole tile."""
    lon_lat = parameters.get_center_global()
    path_to_btg = ct.construct_path_to_files(parameters.PATH_TO_SCENERY, scenery_directory_name(SceneryType.terrain),
                                             (lon_lat.lon, lon_lat.lat))
    tile_index = parameters.get_tile_index()

    # from cartesian ellipsoid to geodetic flat
    trans_proj = pyproj.Transformer.from_crs({"proj": 'geocent', "ellps": 'WGS84', "datum": 'WGS84'}, "EPSG:4326")

    file_name = ct.construct_btg_file_name_from_tile_index(tile_index)
    if airport_code:
        file_name = ct.construct_btg_file_name_from_airport_code(airport_code)
    btg_file_name = os.path.join(path_to_btg, file_name)
    if not os.path.isfile(btg_file_name):
        logging.warning('File %s does not exist. Ocean or missing in Terrasync?', btg_file_name)
        return None
    logging.debug('Reading btg file: %s', btg_file_name)
    btg_reader = BTGReader(btg_file_name, True if airport_code is not None else False)

    gbs_center = btg_reader.gbs_center

    v_max_x = 0
    v_max_y = 0
    v_max_z = 0
    v_min_x = 0
    v_min_y = 0
    v_min_z = 0
    for vertex in btg_reader.vertices:
        if vertex.x >= 0:
            v_max_x = max(v_max_x, vertex.x)
        else:
            v_min_x = min(v_min_x, vertex.x)
        if vertex.y >= 0:
            v_max_y = max(v_max_y, vertex.y)
        else:
            v_min_y = min(v_min_y, vertex.y)
        if vertex.z >= 0:
            v_max_z = max(v_max_z, vertex.z)
        else:
            v_min_z = min(v_min_z, vertex.z)

        # translate to lon_lat and then to local coordinates
        lat, lon, _alt = trans_proj.transform(vertex.x + gbs_center.x,
                                              vertex.y + gbs_center.y,
                                              vertex.z + gbs_center.z,
                                              radians=False)

        vertex.x, vertex.y = transformer.to_local((lon, lat))
        vertex.z = _alt

    return btg_reader
Exemplo n.º 8
0
 def line_string_from_osm_way(self, nodes_dict: Dict[int, Node], transformer: Transformation) \
         -> Optional[shg.LineString]:
     my_coordinates = list()
     for ref in self.refs:
         if ref in nodes_dict:
             my_node = nodes_dict[ref]
             x, y = transformer.to_local((my_node.lon, my_node.lat))
             my_coordinates.append((x, y))
     if len(my_coordinates) >= 2:
         my_geometry = shg.LineString(my_coordinates)
         if my_geometry.is_valid and not my_geometry.is_empty:
             return my_geometry
     return None
Exemplo n.º 9
0
def match_local_coords_with_global_nodes(
        local_list: List[Tuple[float, float]],
        ref_list: List[int],
        all_nodes: Dict[int, op.Node],
        coords_transform: co.Transformation,
        osm_id: int,
        create_node: bool = False) -> List[int]:
    """Given a set of coordinates in local space find matching Node objects in global space.
    Matching is using a bit of tolerance (cf. parameter), which should be enough to account for conversion precision
    resp. float precision.
    If a node cannot be matched: if parameter create_node is False, then a ValueError is thrown - else a new
    Node is created and added to the all_nodes dict.
    """
    matched_nodes = list()
    nodes_local = dict(
    )  # key is osm_id from Node, value is Tuple[float, float]
    for ref in ref_list:
        node = all_nodes[ref]
        nodes_local[node.osm_id] = coords_transform.to_local(
            (node.lon, node.lat))

    for local in local_list:
        closest_distance = 999999
        found_key = -1
        for key, node_local in nodes_local.items():
            distance = co.calc_distance_local(local[0], local[1],
                                              node_local[0], node_local[1])
            if distance < closest_distance:
                closest_distance = distance
            if distance < parameters.TOLERANCE_MATCH_NODE:
                found_key = key
                break
        if found_key < 0:
            if create_node:
                lon, lat = coords_transform.to_global(local)
                new_node = op.Node(
                    op.get_next_pseudo_osm_id(
                        op.OSMFeatureType.building_relation), lat, lon)
                all_nodes[new_node.osm_id] = new_node
                matched_nodes.append(new_node.osm_id)
            else:
                raise ValueError(
                    'No match for parent with osm_id = %d. Closest: %f' %
                    (osm_id, closest_distance))
        else:
            matched_nodes.append(found_key)

    return matched_nodes
Exemplo n.º 10
0
 def polygon_from_osm_way(self, nodes_dict: Dict[int, Node], my_coord_transformator: Transformation) \
         -> Optional[shg.Polygon]:
     """Creates a shapely polygon in local coordinates. Or None is something is not valid."""
     my_coordinates = list()
     for ref in self.refs:
         if ref in nodes_dict:
             my_node = nodes_dict[ref]
             x, y = my_coord_transformator.to_local((my_node.lon, my_node.lat))
             my_coordinates.append((x, y))
     if len(my_coordinates) >= 3:
         my_polygon = shg.Polygon(my_coordinates)
         if not my_polygon.is_valid:  # it might be self-touching or self-crossing polygons
             clean = my_polygon.buffer(0)  # cf. http://toblerity.org/shapely/manual.html#constructive-methods
             if clean.is_valid:
                 my_polygon = clean  # it is now a Polygon or a MultiPolygon
         if my_polygon.is_valid and not my_polygon.is_empty:
             return my_polygon
     return None
Exemplo n.º 11
0
    def __init__(self,
                 transform: co.Transformation,
                 way: op.Way,
                 nodes_dict: Dict[int, op.Node],
                 lit_areas: List[shg.Polygon],
                 width: float,
                 tex_coords: Tuple[float, float] = road.EMBANKMENT_1):
        self.width = width
        self.way = way
        self.nodes_dict = nodes_dict
        self.written_to_ac = False

        self.vectors = None  # numpy array defined in compute_angle_etc()
        self.normals = None  # numpy array defined in compute_angle_etc()
        self.angle = None  # numpy array defined in compute_angle_etc()
        self.segment_len = None  # numpy array defined in compute_angle_etc()
        self.dist = None  # numpy array defined in compute_angle_etc()

        osm_nodes = [nodes_dict[r] for r in way.refs]
        nodes = np.array(
            [transform.to_local((n.lon, n.lat)) for n in osm_nodes])
        self.center = shg.LineString(nodes)
        self.lighting = list()  # same number of elements as self.center
        self._prepare_lighting(nodes, lit_areas)
        try:
            self._compute_angle_etc()
            self.left, self.right = self._compute_sides(self.width /
                                                        2.)  # LineStrings
        except Warning as reason:
            logging.warning("Warning in OSM_ID %i: %s", self.way.osm_id,
                            reason)
        self.tex = tex_coords  # determines which part of texture we use

        # set in roads.py
        self.cluster_ref = None  # Cluster
        self.junction0 = None  # utils.graph.Junction
        self.junction1 = None  # utils.graph.Junction
Exemplo n.º 12
0
def _create_pseudo_stg_entries_for_exclude_areas(
        areas: Optional[List[List[Tuple[float, float]]]],
        transform: Transformation) -> List[STGEntry]:
    """Create a list of faked STGEntries for exclude areas.

    We cannot control the data quality of the user provided input, so no recovery on error -> fail fast.
    """
    if areas is None or len(areas) == 0:
        return list()
    faked_entries = list()
    for i, list_of_tuples in enumerate(areas):
        my_coordinates = list()
        for lon_lat in list_of_tuples:
            x, y = transform.to_local((lon_lat[0], lon_lat[1]))
            my_coordinates.append((x, y))
        if len(my_coordinates) >= 3:
            my_polygon = shg.Polygon(my_coordinates)
            if my_polygon.is_valid and not my_polygon.is_empty:
                lon, lat = transform.to_global(
                    (my_polygon.centroid.x, my_polygon.centroid.y))
                my_entry = STGEntry(STGVerbType.object_static.name,
                                    'exclude area', 'fake', lon, lat, 0, 0)
                my_entry.convex_hull = my_polygon
                faked_entries.append(my_entry)
            else:
                raise ValueError(
                    'Resulting exclude area polygon is not valid or empty: Entry: %i',
                    i + 1)
        else:
            raise ValueError(
                'There must be at least 3 coordinate tuples per exclude area polygon. Entry: %i',
                i + 1)
    logging.info(
        'Added %i fake static STGEntries for OVERLAP_CHECK_EXCLUDE_AREAS',
        len(faked_entries))
    return faked_entries
Exemplo n.º 13
0
def process_polygons_from_btg_faces(btg_reader: BTGReader, materials: List[str], exclusion_materials: bool,
                                    transformer: Transformation, merge_polys: bool = True) -> Dict[str, List[Polygon]]:
    """For a given set of BTG materials merge the faces read from BTG into as few polygons as possible.
    Parameter exclusion_materials means whether the list of materials is for excluding or including in outcome."""
    btg_lon, btg_lat = btg_reader.gbs_lon_lat
    btg_x, btg_y = transformer.to_local((btg_lon, btg_lat))
    logging.debug('Difference between BTG and transformer: x = %f, y = %f', btg_x, btg_y)
    if exclusion_materials:  # airport has same origin as tile
        btg_x = 0
        btg_y = 0

    btg_polys = dict()
    min_x, min_y = transformer.to_local((parameters.BOUNDARY_WEST, parameters.BOUNDARY_SOUTH))
    max_x, max_y = transformer.to_local((parameters.BOUNDARY_EAST, parameters.BOUNDARY_NORTH))
    bounds = (min_x, min_y, max_x, max_y)

    disjoint = 0
    accepted = 0
    counter = 0
    merged_counter = 0

    for key, faces_list in btg_reader.faces.items():
        if (exclusion_materials and key not in materials) or (exclusion_materials is False and key in materials):
            temp_polys = list()
            for face in faces_list:
                counter += 1
                v0 = btg_reader.vertices[face.vertices[0]]
                v1 = btg_reader.vertices[face.vertices[1]]
                v2 = btg_reader.vertices[face.vertices[2]]
                # create the triangle polygon
                my_geometry = Polygon([(v0.x - btg_x, v0.y - btg_y), (v1.x - btg_x, v1.y - btg_y),
                                       (v2.x - btg_x, v2.y - btg_y), (v0.x - btg_x, v0.y - btg_y)])
                if not my_geometry.is_valid:  # it might be self-touching or self-crossing polygons
                    clean = my_geometry.buffer(0)  # cf. http://toblerity.org/shapely/manual.html#constructive-methods
                    if clean.is_valid:
                        my_geometry = clean  # it is now a Polygon or a MultiPolygon
                    else:  # lets try with a different sequence of points
                        my_geometry = Polygon([(v0.x - btg_x, v0.y - btg_y), (v2.x - btg_x, v2.y - btg_y),
                                               (v1.x - btg_x, v1.y - btg_y), (v0.x - btg_x, v0.y - btg_y)])
                        if not my_geometry.is_valid:
                            clean = my_geometry.buffer(0)
                            if clean.is_valid:
                                my_geometry = clean
                if isinstance(my_geometry, Polygon) and my_geometry.is_valid and not my_geometry.is_empty:
                    if not disjoint_bounds(bounds, my_geometry.bounds):
                        temp_polys.append(my_geometry)
                        accepted += 1
                    else:
                        disjoint += 1
                else:
                    pass  # just discard the triangle

            # merge polygons as much as possible in order to reduce processing and not having polygons
            # smaller than parameters.OWBB_GENERATE_LANDUSE_LANDUSE_MIN_AREA
            if merge_polys:
                merged_list = merge_buffers(temp_polys)
                merged_counter += len(merged_list)
                btg_polys[key] = merged_list
            else:
                btg_polys[key] = temp_polys

    logging.debug('Out of %i faces %i were disjoint and %i were accepted with the bounds.',
                  counter, disjoint, accepted)
    logging.info('Number of polygons found: %i. Used materials: %s (excluding: %s)', counter, str(materials),
                 str(exclusion_materials))
    if merge_polys:
        logging.info('These were reduced to %i polygons', merged_counter)
    return btg_polys
Exemplo n.º 14
0
def read_stg_entries_in_boundary(transform: Transformation,
                                 for_roads: bool) -> List[STGEntry]:
    """Returns a list of all STGEntries within the boundary according to parameters.
    Adds all tiles bordering the chosen tile in order to make sure that objects crossing tile borders but
    maybe located outside also are taken into account.
    It uses the PATH_TO_SCENERY and PATH_TO_SCENERY_OPT (which are static), not PATH_TO_OUTPUT.
    If my_cord_transform is set, then for each entry the convex hull is calculated in local coordinates.
    """
    bucket_span = calc_tile.bucket_span(
        parameters.BOUNDARY_NORTH -
        (parameters.BOUNDARY_NORTH - parameters.BOUNDARY_SOUTH) / 2)
    boundary_west = parameters.BOUNDARY_WEST - bucket_span
    boundary_east = parameters.BOUNDARY_EAST + bucket_span
    boundary_north = parameters.BOUNDARY_NORTH + 1. / 8.
    boundary_south = parameters.BOUNDARY_SOUTH - 1. / 8.
    stg_entries = list()
    stg_files = calc_tile.get_stg_files_in_boundary(
        boundary_west, boundary_south, boundary_east, boundary_north,
        parameters.PATH_TO_SCENERY, "Objects")

    if parameters.PATH_TO_SCENERY_OPT:
        for my_path in parameters.PATH_TO_SCENERY_OPT:
            stg_files_opt = calc_tile.get_stg_files_in_boundary(
                boundary_west, boundary_south, boundary_east, boundary_north,
                my_path, "Objects")
            stg_files.extend(stg_files_opt)

    for filename in stg_files:
        stg_entries.extend(read_stg_entries(filename))

    # exclude entries in skip list
    for entry in reversed(stg_entries):
        if entry.obj_filename in parameters.SKIP_LIST_OVERLAP:
            stg_entries.remove(entry)

    # the border of the original tile in local coordinates
    south_west = transform.to_local(
        (parameters.BOUNDARY_WEST, parameters.BOUNDARY_SOUTH))
    north_east = transform.to_local(
        (parameters.BOUNDARY_EAST, parameters.BOUNDARY_NORTH))
    tile_box = shg.box(south_west[0], south_west[1], north_east[0],
                       north_east[1])

    _parse_stg_entries_for_convex_hull(stg_entries, transform, tile_box)

    # after having all original stg-entries, lets check for exclude areas
    if for_roads:
        areas = parameters.OVERLAP_CHECK_EXCLUDE_AREAS_ROADS
    else:
        areas = parameters.OVERLAP_CHECK_EXCLUDE_AREAS_BUILDINGS
    exclude_area_entries = _create_pseudo_stg_entries_for_exclude_areas(
        areas, transform)
    # remove all static entries within the exclude areas - as they might have problems in their geometry
    count_removed = 0
    for entry in reversed(stg_entries):
        if entry.verb_type is not STGVerbType.object_static:
            continue
        x, y = transform.to_local((entry.lon, entry.lat))
        for fake_entry in exclude_area_entries:
            if fake_entry.convex_hull.contains(shg.Point(x, y)):
                stg_entries.remove(entry)
                count_removed += 1
                break
    # finally add the fake exclude areas to the list of entries
    logging.info(
        'Removed %i static object entries due to OVERLAP_CHECK_EXCLUDE_AREAS',
        count_removed)
    stg_entries.extend(exclude_area_entries)
    return stg_entries