PhysicalAsset#
- class rapidtools.core.PhysicalAsset(*, id: str, geometry: BaseGeometry, attributes: dict[str, ~typing.Any]=<factory>, image_assets: ImageCollection = <factory>)[source]#
Bases:
objectRepresents a tangible, real-world entity.
A
PhysicalAssetserves as the digital twin for a real-world object (e.g., a utility pole, building, or road segment). It acts as a container combining:Spatial Data: Where it is located (Shapely geometry).
Metadata: What describes it (Attributes dictionary).
Media: Visual records of it (List of ImageAssets).
This distinguishes the physical object itself from the digital media (
ImageAsset) used to document it.Example
To create a simple
PhysicalAssetfor a utility pole:>>> from shapely.geometry import Point >>> from rapidtools.core import PhysicalAsset >>> >>> my_asset = PhysicalAsset( ... id='pole_123', ... geometry=Point(10.0, 20.0), ... attributes={'type': 'utility_pole', 'material': 'wood'} ... )
Methods
Add or update the asset’s attributes using values from a dictionary.
Add one or more
ImageAssetobjects to the asset.Create a
PhysicalAssetfrom a GeoJSON feature dictionary.Retrieve one or more descriptive attributes from the asset.
Retrieve specific image assets by their ID or filename.
Remove one or more attributes from the asset by key.
Removes image assets identified by their unique string ID or filename.
Print a human-readable summary of the asset information.
Converts the asset into a GeoJSON feature dictionary.
- property asset_type: str | None#
A convenience property to get the asset type from attributes.
It searches a predefined list of keys (e.g.,
'asset_type','type') and returns the value of the first key found. ReturnsNoneif none of the keys are present.Example
Create an asset using the key
'category'for its type:>>> from shapely.geometry import Point >>> from rapidtools.core import PhysicalAsset >>> asset = PhysicalAsset( ... id='bldg_01', ... geometry=Point(0, 0), ... attributes={'category': 'commercial', 'floors': 5} ... )
The property automatically finds
'commercial'despite the key being'category':>>> print(asset.asset_type) commercial
- add_attributes(new_attributes: dict[str, Any], overwrite: bool = False) None[source]#
Add or update the asset’s attributes using values from a dictionary.
By default, existing keys are not overwritten. Set
overwrite=Trueto replace existing values.- Parameters:
- Raises:
TypeError – If
new_attributesis not a dictionary.
Example
Initialize an asset with some base attributes:
>>> from shapely.geometry import Point >>> from rapidtools.core import PhysicalAsset >>> asset = PhysicalAsset( ... id='utility_pole_01', ... geometry=Point(0, 0), ... attributes={'material': 'wood', 'status': 'active'} ... )
Add new attributes that do not overlap with the existing ones:
>>> asset.add_attributes({'install_date': '2023-01-01'}) >>> print(asset.attributes['install_date']) 2023-01-01
Attempt to add attributes where some keys are already present in the existing set without specifying the
overwriteargument (i.e., default behavior):>>> asset.add_attributes({'material': 'metal', 'height': 12.0}) INFO: Attribute 'material' already exists in asset 'utility_pole_01'. Skipping attribute as 'overwrite is set to False. >>> print(asset.attributes['material']) wood >>> print(asset.attributes['height']) 12.0
Force update existing attributes by setting the
overwriteargument toTrue:>>> asset.add_attributes({'status': 'retired'}, overwrite=True) >>> print(asset.attributes['status']) retired
- add_image_assets(*args: Any) None[source]#
Add one or more
ImageAssetobjects to the asset.This method is highly flexible and accepts:
Individual
ImageAssetobjects.Lists or Tuples of
ImageAssetobjects.ImageCollectioninstances.A mix of all the above.
Any nested item that is not an
ImageAssetis ignored and a warning is logged.- Parameters:
*args (Any) – Variable length argument list containing images or collections of images.
Examples
Initialize the main physical asset:
>>> from shapely.geometry import Point >>> from rapidtools.core import PhysicalAsset, ImageAsset, >>> ImageCollection >>> >>> asset = PhysicalAsset(id='pole_01', geometry=Point(10, 20))
Initialize several
ImageAssetandImageCollectionobjects to use in the examples below. At minimum, anImageAssetrequires an id and a path. Because we will be using mock paths, we must setallow_missing_file=True; otherwise,ImageAssetwill validate the path, detect that it does not exist, and raise an error. To avoid repeating the path andallow_missing_filearguments, we will define a small helper function, namedmake_imgto constructImageAssetinstances consistently:>>> def make_img(uid): ... return ImageAsset( ... id=uid, ... path=f'/tmp/{uid}', ... allow_missing_file=True ... ) >>> >>> img1 = make_img('img_1.jpg') >>> img2 = make_img('img_2.jpg') >>> img3 = make_img('img_3.jpg') >>> img4 = make_img('img_4.jpg') >>> img5 = make_img('img_5.jpg') >>> img6 = make_img('img_6.jpg') >>> img7 = make_img('img_7.jpg') >>> collection = ImageCollection([img4, img5])
Add individual
ImageAssetsto the physical asset:>>> asset.add_image_assets(img1, img2) >>> len(asset.image_assets) 2
Add a list of images:
>>> asset.add_image_assets([img3]) >>> len(asset.image_assets) 3
Add a mix of a single
ImageAsset, list, tuple, andImageCollectionin one call. Please note that ‘img5.jpg’ is skipped below as it is a duplicate asset:>>> asset.add_image_assets( ... img5, # Single Object ... [img6], # List ... (img7,), # Tuple ... collection # ImageCollection (contains img4, img5) ... ) INFO: Skipping duplicate asset with ID: img_5.jpg >>> 'img_7.jpg' in asset.image_assets.get_ids() True
- classmethod from_geojson_feature(geojson_feature: dict[str, Any], asset_id: str | None = None) PhysicalAsset[source]#
Create a
PhysicalAssetfrom a GeoJSON feature dictionary.The method expects a GeoJSON object of type “Feature” with at least a “geometry” field containing a valid GeoJSON geometry.
If the feature’s properties contain an
'image_assets'list (as produced byto_geojson_feature), this method attempts to reconstruct the correspondingImageAssetobjects and populate the asset’s image collection.If no id is found at the top level or in properties, a warning is logged and a random UUID is generated as a placeholder.
- Parameters:
- Returns:
PhysicalAsset – An initialized physical asset instance.
- Raises:
ValueError – If geojson_feature is not a valid GeoJSON feature (i.e., its “type” is not “Feature”).
Examples
Create a
PhysicalAssetfrom a GeoJSON feature:>>> from rapidtools.core import PhysicalAsset >>> >>> feature = { ... "type": "Feature", ... "id": "pole_01", ... "geometry": {"type": "Point", "coordinates": [10, 20]}, ... "properties": { ... "material": "wood", ... "image_assets": [ ... {"id": "img1", "path": "/tmp/a.jpg", ... "allow_missing_file": True} ... ] ... } ... } >>> >>> asset = PhysicalAsset.from_geojson_feature(feature)
Display the asset’s ID and material properties. Note that
image_assetsis removed from the GeoJSON feature attributes and stored instead in the asset’sImageCollection:>>> print(asset.id) pole_01 >>> print(asset.attributes['material']) wood >>> print('image_assets' in asset.attributes) False >>> print(len(asset.image_assets)) 1
- get_attributes(*keys: str) dict[str, Any][source]#
Retrieve one or more descriptive attributes from the asset.
If a requested key does not exist, a warning is logged and the key is omitted from the returned dictionary.
- Parameters:
*keys (str) – A variable number of keys to retrieve.
- Returns:
dict[str, Any] – A dictionary containing the keys that were found and their values.
Examples
Initialize the main physical asset:
>>> from shapely.geometry import Point >>> from rapidtools.core import PhysicalAsset >>> >>> asset = PhysicalAsset( ... id='bldg_001', ... geometry=Point(0,0), ... attributes={'year_built': 1990, 'material': 'concrete'} ... )
Retrieve a single attribute:
>>> asset.get_attributes('year_built') {'year_built': 1990}
Request multiple attributes. Please note that
'roof_type'is missing, so it is omitted in the result:>>> asset.get_attributes('year_built', 'roof_type', 'material') {'year_built': 1990, 'material': 'concrete'}
- get_image_assets(*identifiers: str) list[ImageAsset][source]#
Retrieve specific image assets by their ID or filename.
This method searches the asset’s image collection. It matches against the
'id'attribute or the'filename'(explicit or derived from path).- Parameters:
*identifiers (str) – A variable number of string IDs or filenames to retrieve.
- Returns:
list[ImageAsset] – A list of the found
ImageAssetobjects. If a requested identifier is not found, it is skipped and a warning is logged.
Examples
Setup asset with images:
>>> from rapidtools.core import PhysicalAsset, ImageAsset >>> from shapely.geometry import Point >>> >>> asset = PhysicalAsset(id='pole_01', geometry=Point(0,0)) >>> img1 = ImageAsset(id='img_A', path='/tmp/photo_A.jpg', ... allow_missing_file=True) >>> img2 = ImageAsset(id='img_B', path='/tmp/photo_B.jpg', ... allow_missing_file=True) >>> asset.add_image_assets(img1, img2)
Get by ID:
>>> found = asset.get_image_assets('img_A') >>> found[0].path PosixPath('/tmp/photo_A.jpg')
Get by filename:
>>> found = asset.get_image_assets('photo_B.jpg') >>> found[0].id 'img_B'
Get multiple (mixed ID and filename):
>>> found = asset.get_image_assets('img_A', 'photo_B.jpg') >>> len(found) 2
Attempt to get non-existent image:
>>> asset.get_image_assets('missing.jpg') WARNING: Image asset 'missing.jpg' not found in asset 'pole_01'. []
- remove_attributes(*keys: str) None[source]#
Remove one or more attributes from the asset by key.
For each provided key:
If the key exists, it is removed.
If the key does not exist, a warning is logged to indicate a potential typo or logic error in the caller.
- Parameters:
*keys (str) – The names of the attributes to remove.
Examples
Setup a physical asset with initial attributes:
>>> from shapely.geometry import Point >>> from rapidtools.core import PhysicalAsset >>> >>> asset = PhysicalAsset( ... id='bldg_123', ... geometry=Point(0, 0), ... attributes={'color': 'red', 'height': 50, 'year': 1990} ... )
Remove a single existing attribute:
>>> asset.remove_attributes('color') >>> 'color' in asset.attributes False
Attempt to remove a non-existent attribute:
>>> asset.remove_attributes('width') WARNING: Attempted to remove non-existent attribute 'width' from asset 'bldg_123'. >>> print(asset.attributes) {'height': 50, 'year': 1990}
Attempt to remove multiple (mixed existing and missing) attributes at once:
>>> asset.remove_attributes('height', 'invalid_key') WARNING: Attempted to remove non-existent attribute 'invalid_key' from asset 'bldg_123'. >>> print(asset.attributes) {'year': 1990}
- remove_image_assets(*image_ids: str) list[ImageAsset][source]#
Removes image assets identified by their unique string ID or filename.
This method searches the asset’s image collection for matches. It checks both the
'id'and'file_name'fields of the image assets.- Parameters:
*image_ids – A variable number of string IDs (or filenames) identifying the images to remove.
- Returns:
list[ImageAsset] – A list containing the actual
ImageAssetobjects that were successfully found and removed.
Examples
Setup asset with images:
>>> from rapidtools.core import PhysicalAsset, ImageAsset >>> from shapely.geometry import Point >>> >>> asset = PhysicalAsset(id='pole_01', geometry=Point(0,0)) >>> img1 = ImageAsset(id='img_A', path='/tmp/photo_A.jpg', ... allow_missing_file=True) >>> img2 = ImageAsset(id='img_B', path='/tmp/photo_B.jpg', ... allow_missing_file=True) >>> img3 = ImageAsset(id='img_C', path='/tmp/photo_C.jpg', ... allow_missing_file=True) >>> asset.add_image_assets(img1, img2, img3)
Remove by ID:
>>> removed = asset.remove_image_assets('img_A') >>> len(asset.image_assets) 2 >>> removed[0].id 'img_A'
Remove by filename (assuming
file_nameis derived from path):>>> removed = asset.remove_image_assets('photo_B.jpg') >>> len(asset.image_assets) 1
Attempt to remove non-existent image:
>>> asset.remove_image_assets('ghost_image.jpg') WARNING: Attempted to remove image 'ghost_image.jpg' from asset 'pole_01', but no matching asset was found. []
- summary() None[source]#
Print a human-readable summary of the asset information.
The summary includes:
Identity (ID and inferred type).
Spatial data (WKT geometry, truncated if excessively long).
Metadata (Attributes pretty-printed as JSON).
Media (List of associated ImageAsset representations).
Example
>>> from shapely.geometry import Point >>> from rapidtools.core import PhysicalAsset >>> >>> asset = PhysicalAsset( ... id='pole_99', ... geometry=Point(10, 20), ... attributes={'material': 'wood', 'install_year': 2021} ... ) >>> >>> asset.summary() --- PhysicalAsset Summary --- ID: pole_99 Asset Type: N/A Geometry: POINT (10 20) Attributes (2): { "material": "wood", "install_year": 2021 } Image Assets (0): (No image assets) -----------------------------
- to_geojson_feature(ignore_properties: list[str] | None = None) dict[str, Any][source]#
Converts the asset into a GeoJSON feature dictionary.
The asset’s
'id'becomes the feature’s'id'. The asset’s ‘attributes’ are used as the base for the feature’s ‘properties’. The list of'image_assets'is serialized and added to the ‘properties’ by default.Specific properties can be excluded from the output using the
ignore_propertiesargument, which is useful for reducing file size when large nested objects are not required.- Parameters:
ignore_properties (list[str] or None, optional) – A list of property keys to exclude from the GeoJSON properties dictionary. Commonly used to exclude
'image_assets'. Defaults toNone.- Returns:
dict[str, Any] – A dictionary representing a valid GeoJSON Feature.
Examples
Create asset with an image:
>>> from shapely.geometry import Point >>> from rapidtools.core import PhysicalAsset, ImageAsset >>> >>> asset = PhysicalAsset( ... id='pole_99', ... geometry=Point(10, 20), ... attributes={'material': 'wood'} ... ) >>> img = ImageAsset(id='img1', path='p1.jpg', ... allow_missing_file=True) >>> asset.add_image_assets(img)
Convert to GeoJSON:
>>> feature = asset.to_geojson_feature() >>> print(feature) {'type': 'Feature', 'id': 'pole_99', 'geometry': {'type': 'Point', 'coordinates': (10.0, 20.0)}, 'properties': {'material': 'wood', 'image_assets': [{'id': 'img1', 'path': '/home/bacetiner/p1.jpg', 'properties': {}, 'semantic_map': None, 'instance_map': None, 'allow_missing_file': True}]}}