def __init__(self, pbf_path): osmium.SimpleHandler.__init__(self) self.wkbfab = osmium.geom.WKBFactory() self.df = [] self.road_types = [ 'motorway', 'trunk', 'primary', 'secondary', 'tertiary', 'road', 'residential', 'service', 'motorway_link', 'trunk_link', 'primary_link', 'secondary_link', 'tertiary_link' ] print(f'loading {pbf_path}...') self.apply_file(pbf_path, locations=True) cols = ['way_id', 'nodes', 'line', 'line_length', 'name', 'maxspeed'] self.df = pd.DataFrame(self.df, columns=cols).set_index('way_id') not_numeric_flag = ~self.df['maxspeed'].astype(str).str.isnumeric() self.df.loc[not_numeric_flag, 'maxspeed'] = '0' self.df['maxspeed'] = self.df['maxspeed'].astype(int) print('creating spatial index...') # Populate R-tree index with bounds of grid cells self.r_tree = index.Index() pols = [] for way_id, row in self.df.iterrows(): p = Polygon(row['line'].buffer(.00005).exterior.coords) p.maxspeed = row['maxspeed'] p.way_id = way_id p.name = row['name'] pols.append(p) self.r_tree.insert(way_id, p.bounds) self.df = gpd.GeoDataFrame(self.df, geometry=pols) print(f'finished')
def _rect_grid_to_shape_list(self): """ internal method, convert structured grid to list of shapely polygons Returns ------- list list of shapely Polygons """ if shapely is None: msg = 'GridIntersect()._rect_grid_to_shape_list(): error ' + \ 'importing shapely - try "pip install shapely"' raise ImportError(msg) else: from shapely.geometry import Polygon shplist = [] for i in range(self.mfgrid.nrow): for j in range(self.mfgrid.ncol): xy = self.mfgrid.get_cell_vertices(i, j) p = Polygon(xy) p.name = (i, j) shplist.append(p) return shplist
def parse_polygon(self, name, description): _points = description['corners'] for points in itertools.permutations(_points): polygon = Polygon(points) polygon.name = name if polygon.is_valid: return polygon
def _vtx_grid_to_shape_list(self): """ internal method, convert vertex grid to list of shapely polygons Returns ------- list list of shapely Polygons """ if shapely is None: msg = 'GridIntersect()._vtx_grid_to_shape_list(): error ' + \ 'importing shapely - try "pip install shapely"' raise ImportError(msg) else: from shapely.geometry import Polygon shplist = [] if isinstance(self.mfgrid._cell2d, np.recarray): for icell in self.mfgrid._cell2d.icell2d: points = [] for iv in self.mfgrid._cell2d[[ "icvert_0", "icvert_1", "icvert_2" ]][icell]: points.append((self.mfgrid._vertices.xv[iv], self.mfgrid._vertices.yv[iv])) # close the polygon, if necessary if points[0] != points[-1]: points.append(points[0]) p = Polygon(points) p.name = icell shplist.append(p) elif isinstance(self.mfgrid._cell2d, list): for icell in range(len(self.mfgrid._cell2d)): points = [] for iv in self.mfgrid._cell2d[icell][-3:]: points.append((self.mfgrid._vertices[iv][1], self.mfgrid._vertices[iv][2])) # close the polygon, if necessary if points[0] != points[-1]: points.append(points[0]) p = Polygon(points) p.name = icell shplist.append(p) return shplist
def addShapeAsHole(self, name='undefined'): """Adds a hole in the shape of the current shape with the current position""" newhole = Polygon(self.getShapeOriented()) if self.convex_hull: newhole = newhole.convex_hull newhole.shape_nfps = defaultdict() newhole.name = name newhole.position = self.position newhole.angle = self.angle newhole.origin = affinity.rotate(Point(-self.circle_center[0], -self.circle_center[1]), self.angle, origin=(0,0)) newhole.origin = affinity.translate(newhole.origin, self.position[0], self.position[1]) self.hole_shapes.append(newhole)
def _vtx_grid_to_shape_list(self): """ internal method, convert vertex grid to list of shapely polygons Returns ------- list list of shapely Polygons """ shplist = [] if isinstance(self.mfgrid._cell2d, np.recarray): for icell in self.mfgrid._cell2d.icell2d: points = [] icverts = ["icvert_{}".format(i) for i in range(self.mfgrid._cell2d["ncvert"][icell])] for iv in self.mfgrid._cell2d[icverts][icell]: points.append((self.mfgrid._vertices.xv[iv], self.mfgrid._vertices.yv[iv])) # close the polygon, if necessary if points[0] != points[-1]: points.append(points[0]) p = Polygon(points) p.name = icell shplist.append(p) elif isinstance(self.mfgrid._cell2d, list): for icell in range(len(self.mfgrid._cell2d)): points = [] for iv in self.mfgrid._cell2d[icell][-3:]: points.append((self.mfgrid._vertices[iv][1], self.mfgrid._vertices[iv][2])) # close the polygon, if necessary if points[0] != points[-1]: points.append(points[0]) p = Polygon(points) p.name = icell shplist.append(p) return shplist
def _rect_grid_to_shape_list(self): """internal method, convert structured grid to list of shapely polygons Returns ------- list list of shapely Polygons """ shplist = [] for i in range(self.mfgrid.nrow): for j in range(self.mfgrid.ncol): xy = self.mfgrid.get_cell_vertices(i, j) p = Polygon(xy) p.name = (i, j) shplist.append(p) return shplist
def from_shape(cls, shape, height=0., name="area", properties=None, unit='um', min_x=None, max_x=None): ''' Create an :class:`Area` from a :class:`Shape` object. Parameters ---------- shape : :class:`Shape` Shape that should be converted to an Area. Returns ------- :class:`Area` object. ''' if _unit_support: from .units import Q_ if isinstance(height, Q_): height = height.m_as(unit) if isinstance(min_x, Q_): min_x = min_x.m_as(unit) if isinstance(max_x, Q_): max_x = max_x.m_as(unit) obj = None g_type = None if isinstance(shape, MultiPolygon): g_type = "MultiPolygon" elif isinstance(shape, (Polygon, Shape, Area)): g_type = "Polygon" else: raise TypeError("Expected a Polygon or MultiPolygon object.") # find the scaling factor scaling = 1. if None not in (min_x, max_x): ext = np.array(shape.exterior.coords) leftmost = np.min(ext[:, 0]) rightmost = np.max(ext[:, 0]) scaling = (max_x - min_x) / (rightmost - leftmost) obj = scale(shape, scaling, scaling) else: if g_type == "Polygon": obj = Polygon(shape) else: obj = MultiPolygon(shape) obj.__class__ = cls obj._parent = None obj._unit = unit obj._geom_type = g_type obj.__class__ = Area obj._areas = None obj.height = height obj.name = name obj._prop = _PDict({} if properties is None else deepcopy(properties)) obj._return_quantity = False return obj
def xsec_to_2d(self, xsec_name: str, lunit: Optional[str] = None) -> Geo2DData: """ Generates a Geo2DData from a cross section :param xsec_name: Name of the cross section :return: Geo2DData object """ # Get our new coordinates # This construction ensures that y_new (the first axis in our new 2d coodinate # system) is always aligned (by projection) to one of the old axes, prefering # x, then y, then z x_new = np.array(self.xsecs[xsec_name]["axis"]) y_new = np.array([0.0, 0, 0]) axis_ind = -1 while not np.any(y_new): axis_ind += 1 y_new[axis_ind] = 1 y_new -= y_new.dot(x_new) * x_new y_new /= np.linalg.norm(y_new) z_new = np.cross(x_new, y_new) def _project(vec): """Projects a 3D vector into our 2D cross section plane""" vec = vec - x_new * self.xsecs[xsec_name]["distance"] return [vec.dot(y_new), vec.dot(z_new)] def _inverse_project(vec): """Inverse of _project""" return (x_new * self.xsecs[xsec_name]["distance"] + vec[0] * y_new + vec[1] * z_new) part_polygons = {} virtual_part_polygons = {} # part_polygons is a dictionary of part_name to polygons. virtual_part_polygons # is the same for virtual parts for part_name in self.build_order: polygons = [] for name, points in self.xsecs[xsec_name]["polygons"].items(): if name.startswith(f"{part_name}_"): poly = Polygon(map(_project, points)) # we add a name to the polygon so we can reference it easier later poly.name = name polygons.append(poly) if polygons: if self.parts[part_name].domain_type == "virtual": virtual_part_polygons[part_name] = polygons else: part_polygons[part_name] = polygons def _build_containment_graph(poly_list): """ Given a list of polygons, build a directed graph where edge A -> B means A contains B. This intentionally does not include nested containment. Which means if A contains B and B contains C, we will only get the edges A -> B and B -> C, not A -> C """ poly_by_area = sorted(poly_list, key=lambda p: p.area) # graph["poly_name"] is a list of polygons that poly_name contains graph = {poly.name: [] for poly in poly_list} for i, poly in enumerate(poly_by_area): # Find the smallest polygon that contains poly (if any), and add it to # the graph for bigger_poly in poly_by_area[i + 1:]: if bigger_poly.contains(poly): graph[bigger_poly.name].append(poly) break return graph def _is_inside(poly, part): """ Given a polygon, find a point inside of it, and then check if that point is in the (3D) part """ # Find the midpoint in x, then find the intersections with the polygon on # that vertical line. Then find the midpoint in y along the first # intersection line (if there're multiple) min_x, min_y, max_x, max_y = poly.bounds x = (min_x + max_x) / 2 x_line = LineString([(x, min_y), (x, max_y)]) intersec = x_line.intersection(poly) if type(intersec) == MultiLineString: intersec = intersec[0] x_line_intercept_min, x_line_intercept_max = intersec.xy[1].tolist( ) y = (x_line_intercept_min + x_line_intercept_max) / 2 # Get 3D coordinates and check if it's in the freecad shape x, y, z = _inverse_project([x, y]) freecad_solid = Part.Solid( FreeCAD.ActiveDocument.getObject(part.built_fc_name).Shape) return freecad_solid.isInside(Base.Vector(x, y, z), 1e-5, True) # Let's deal with the physical domains first, which can have cavities geo_2d = Geo2DData() self.get_data("fcdoc") # The document name is "instance" for name, poly_list in part_polygons.items(): cont_graph = _build_containment_graph(poly_list) polys_to_add = [] # For each polygon (in each part), we subtract from it all interior polygons # And then check if what remains is inside the part or not (it could be a # cavity). We add it if it's not a cavity for poly in poly_list: for interior_poly in cont_graph[poly.name]: poly = poly.difference(interior_poly) if _is_inside(poly, self.parts[name]): polys_to_add.append(poly) if not polys_to_add: continue if len(polys_to_add) == 1: geo_2d.add_part(name, polys_to_add[0]) continue for i, poly in enumerate(polys_to_add): geo_2d.add_part(f"{name}_{i}", poly) # Now we deal with the virtual parts, which are just added as is for name, poly_list in virtual_part_polygons.items(): if len(poly_list) == 1: geo_2d.add_part(name, poly_list[0]) continue for i, poly in enumerate(poly_list): geo_2d.add_part(f"{name}_{i}", poly) geo_2d.lunit = lunit # Clean up freecad document FreeCAD.closeDocument("instance") return geo_2d
def xsec_to_2d(self, xsec_name: str, lunit: Optional[str] = None) -> Geo2DData: """Generates a Geo2DData from a cross section Parameters ---------- xsec_name : str Name of the cross section lunit : Optional[str] : (Default value = None) Returns ------- None """ # Get our new coordinates # This constructions tries to align the new coordinates to our old coordinates # In particular, the map from projection axis -> new axes is # [1,0,0] -> [0,1,0] [0,0,1] # [0,1,0] -> [1,0,0] [0,0,1] # [0,0,1] -> [1,0,0] [0,1,0] # Find out which axis the projection axis is most closely aligned to x_new = np.array(self.xsecs[xsec_name]["axis"]) ind = np.argmax(np.abs(x_new)) y_new = np.array([0, 1.0, 0]) if ind == 0 else np.array([1.0, 0, 0]) y_new -= y_new.dot(x_new) * x_new z_new = np.cross(x_new, y_new) # Adjust our second axis so that the "height axis" is correctly aligned if z_new[2] < 0: z_new = -z_new def _project(vec): """Projects a 3D vector into our 2D cross section plane Parameters ---------- vec : Returns ------- List of projection. """ vec = vec - x_new * self.xsecs[xsec_name]["distance"] return [vec.dot(y_new), vec.dot(z_new)] def _inverse_project(vec): """Inverse of _project Parameters ---------- vec : Returns ------- Float, inverse of _project. """ return (x_new * self.xsecs[xsec_name]["distance"] + vec[0] * y_new + vec[1] * z_new) part_polygons = {} virtual_part_polygons = {} # part_polygons is a dictionary of part_name to polygons. virtual_part_polygons # is the same for virtual parts for part_name in self.build_order: polygons = [] for name, points in self.xsecs[xsec_name]["polygons"].items(): if name.startswith(f"{part_name}_"): poly = Polygon(map(_project, points)) # we add a name to the polygon so we can reference it easier later poly.name = name polygons.append(poly) if polygons: if self.parts[part_name].virtual: virtual_part_polygons[part_name] = polygons else: part_polygons[part_name] = polygons def _build_containment_graph(poly_list): """Given a list of polygons, build a directed graph where edge A -> B means A contains B. This intentionally does not include nested containment. Which means if A contains B and B contains C, we will only get the edges A -> B and B -> C, not A -> C. Parameters ---------- poly_list : Returns ------- graph """ poly_by_area = sorted(poly_list, key=lambda p: p.area) # graph["poly_name"] is a list of polygons that poly_name contains graph = {poly.name: [] for poly in poly_list} for i, poly in enumerate(poly_by_area): # Find the smallest polygon that contains poly (if any), and add it to # the graph for bigger_poly in poly_by_area[i + 1:]: if bigger_poly.contains(poly): graph[bigger_poly.name].append(poly) break return graph def _is_inside(poly, part): """Given a polygon, find a point inside of it, and then check if that point is in the (3D) part Parameters ---------- poly : part : Returns ------- Boolean """ # Find the midpoint in x, then find the intersections with the polygon on # that vertical line. Then find the midpoint in y along the first # intersection line (if there're multiple) min_x, min_y, max_x, max_y = poly.bounds x = (min_x + max_x) / 2 x_line = LineString([(x, min_y), (x, max_y)]) intersec = x_line.intersection(poly) if type(intersec) == MultiLineString: intersec = intersec[0] x_line_intercept_min, x_line_intercept_max = intersec.xy[1].tolist( ) y = (x_line_intercept_min + x_line_intercept_max) / 2 # Get 3D coordinates and check if it's in the freecad shape x, y, z = _inverse_project([x, y]) freecad_solid = Part.Solid( FreeCAD.ActiveDocument.getObject(part.built_fc_name).Shape) return freecad_solid.isInside(Base.Vector(x, y, z), 1e-5, True) # Let's deal with the physical domains first, which can have cavities geo_2d = Geo2DData() self.get_data("fcdoc") # The document name is "instance" for name, poly_list in part_polygons.items(): cont_graph = _build_containment_graph(poly_list) polys_to_add = [] # For each polygon (in each part), we subtract from it all interior polygons # And then check if what remains is inside the part or not (it could be a # cavity). We add it if it's not a cavity for poly in poly_list: for interior_poly in cont_graph[poly.name]: poly = poly.difference(interior_poly) if _is_inside(poly, self.parts[name]): polys_to_add.append(poly) if not polys_to_add: continue if len(polys_to_add) == 1: geo_2d.add_part(name, polys_to_add[0]) continue for i, poly in enumerate(polys_to_add): geo_2d.add_part(f"{name}:{i}", poly) # Now we deal with the virtual parts, which are just added as is for name, poly_list in virtual_part_polygons.items(): if len(poly_list) == 1: geo_2d.add_part(name, poly_list[0]) continue for i, poly in enumerate(poly_list): geo_2d.add_part(f"{name}:{i}", poly) geo_2d.lunit = self.lunit if lunit is None else None # Clean up freecad document FreeCAD.closeDocument("instance") return geo_2d