from __future__ import annotations
import json
import logging
import uuid
from copy import deepcopy
from typing import Literal
import geopandas as gpd
import numpy as np
import pandas as pd
import requests
from shapely.geometry import LineString, Point, Polygon, mapping
from .constants import SOIL_REFERENCE
BAG_WFS_URL = "https://service.pdok.nl/lv/bag/wfs/v2_0"
THRESHOLD = 10000
[docs]def get_buildings_geodataframe(
west: float,
south: float,
east: float,
north: float,
category: Literal["one", "two"] = "two",
monumental: bool = False,
structural_condition: Literal["sensitive", "normal"] = "normal",
vibration_sensitive: bool = False,
thickness: float = 8.0,
foundation_element: Literal[
"shallow foundation", "concrete piles", "timber piles", "steel piles"
] = "shallow foundation",
material_floor: Literal["concrete", "wood"] = "concrete",
feature: Literal[
"bag:pand",
"bag:ligplaats",
"bag:verblijfsobject",
"bag:woonplaats",
"bag:standplaats",
] = "bag:pand",
pagesize: Literal[10, 20, 50, 100, 1000] = 1000,
) -> gpd.GeoDataFrame:
"""
Get a GeoDataFrame with the default values for CUR166 and PrePal methode.
Parameters
----------
west:
west coordinate in rd new (EPSG:28992)
south:
south coordinate in rd new (EPSG:28992)
east:
east coordinate in rd new (EPSG:28992)
north:
north coordinate in rd new (EPSG:28992)
category:
Building category based on the SBR A table 10.1.
monumental:
Has the building structure a monumental status. Based on the SBR A table 10.3.
structural_condition:
Based on the SBR A table 10.2.
vibration_sensitive:
Has the building structure a vibration sensitive foundation. Based on the SBR A chapter 10.2.5.
thickness:
Layer thickness settlement-sensitive layer [m]
foundation_element:
Based on CUR 166 3rd edition table 5.19
material_floor:
Based on CUR 166 3rd edition table 5.20
feature:
default is bag:pand
item in the BAG, see https://www.nationaalgeoregister.nl/geonetwork/srv/dut/catalog.search#/metadata/1c0dcc64-91aa-4d44-a9e3-54355556f5e7
pagesize:
Results per page
Returns
-------
gdf: gpd.GeoDataFrame
"""
if west > east:
raise ValueError("west coordinate is larger than east coordinate")
if south > north:
raise ValueError("south coordinate is larger than north coordinate")
if east - west > THRESHOLD:
raise ValueError(
f"x dimension of the bbox is {east - west} meters, larger than threshold"
)
if north - south > THRESHOLD:
raise ValueError(
f"y dimension of the bbox is {north - south} meters, larger than threshold"
)
wfs_query_params = {
"service": "WFS",
"version": "2.0.0",
"request": "GetFeature",
"pagingEnabled": "True",
"typeName": feature,
"srsname": "EPSG:28992",
"outputFormat": "application/json; subtype=geojson",
"bbox": str(west) + "," + str(south) + "," + str(east) + "," + str(north),
"count": pagesize,
"startindex": 0,
}
paging = True
array = []
while paging:
response = requests.get(
url=BAG_WFS_URL,
headers={"Content-Type": "application/json"},
params=wfs_query_params,
timeout=5,
)
if not response.ok:
raise RuntimeError(response.text)
_gdf = gpd.read_file(json.dumps(response.json()), driver="GeoJSON").to_crs(
"EPSG:28992"
)
array.append(_gdf)
paging = len(_gdf) == int(wfs_query_params["count"])
wfs_query_params["startindex"] = int(wfs_query_params["count"]) + int(
wfs_query_params["startindex"]
)
gdf = gpd.GeoDataFrame(pd.concat(array))
# add default values
gdf["name"] = gdf.index.astype(str)
# sbr-A
gdf["category"] = category
gdf["structuralCondition"] = structural_condition
gdf["vibrationSensitive"] = vibration_sensitive
gdf["thickness"] = thickness
gdf["monumental"] = monumental
# prepal
gdf["buildingDepth"] = np.clip(np.sqrt(gdf.area), 1.0, 18.0)
gdf["buildingDepthVibrationSensitive"] = 1.0
# cur166
gdf["foundationElement"] = foundation_element
gdf["material"] = material_floor
return gdf
[docs]def create_prepal_payload(
buildings: gpd.GeoDataFrame,
location: Polygon | LineString | Point,
pile_shape: Literal["square", "round"],
pile_size: float,
cone_resistance: float,
reduction: float = 0.0,
unit_weight: float = 20,
elastic_modulus_factor: float = 15,
poisson_ratio: float = 0.2,
frequency: float = 20.0,
vibration_type: Literal[
"short-term", "repeated-short-term", "continuous"
] = "continuous",
frequency_vibration_sensitive: float = 40.0,
measurement_type: Literal["indicative", "limited", "extensive"] = "extensive",
hysteretic_damping_barkan: float = -0.05,
) -> dict:
"""
Create payload for VibraCore call `cur166/validation/multi`
Parameters
----------
buildings
GeoDataFrame that holds teh building information. Must have the following columns:
- buildingDepth:
The minimum building depth [m]
- buildingDepthVibrationSensitive:
The minimum building depth [m]
- category
Based on the SBR A table 10.1.
- monumental
Has the building structure a monumental status. Based on the SBR A table 10.3.
- structuralCondition
Based on the SBR A table 10.2.
- thickness
Layer thickness settlement-sensitive layer [m]
- vibrationSensitive
Has the building structure a vibration sensitive foundation. Based on the SBR A chapter 10.2.5.
location:
Location of the source
poisson_ratio:
Poisson’s ratio of the soil [-]
elastic_modulus_factor:
Elastic modulus factor of the soil [-].
unit_weight:
Volume weight of the soil [kN/m^3]
cone_resistance:
Cone resistance [MPa]
reduction:
Reduction of the cone resistance [%]
pile_size:
Size of the pile [m]
pile_shape:
Shape of the pile.
frequency:
The dominate frequency [Hz]
vibration_type
Based on the SBR A table 10.4.
frequency_vibration_sensitive
The dominate frequency for vibration sensitive building [Hz].
measurement_type
Type of measurement based on the SBR A table 9.2.
hysteretic_damping_barkan:
hysteretic damping barkan [m^-1]
Returns
-------
payload: dict
Raises
-------
KeyError:
Missing column names in GeoDataFrame
"""
columns = [
"category",
"structuralCondition",
"vibrationSensitive",
"thickness",
"buildingDepth",
]
if not set(columns).issubset(set(buildings.columns)):
msg = (
f"Column names:{list(set(columns) - set(buildings.columns))} must be in GeoDataFrame. "
f"Found column names: {buildings.columns}"
)
raise KeyError(msg)
payload = {
"buildingInformation": [
{
"geometry": mapping(row.geometry),
"metadata": {"ID": row.get("name", uuid.uuid4().__str__())},
"properties_PrePal": {
"buildingDepth": row["buildingDepth"],
"buildingDepthVibrationSensitive": row.get(
"buildingDepthVibrationSensitive", 1
),
"calculationHeight": None,
},
"properties_SBRa": {
"category": row["category"],
"frequency": frequency,
"frequencyVibrationSensitive": frequency_vibration_sensitive,
"monumental": row["monumental"],
"structuralCondition": row["structuralCondition"],
"thickness": row["thickness"],
"vibrationSensitive": row["vibrationSensitive"],
"vibrationType": vibration_type,
},
}
for i, row in buildings.iterrows()
],
"vibrationSource": {"shape": pile_shape, "size": pile_size},
"soilProperties": {
"coneResistance": cone_resistance * (100 - reduction) / 100,
"elasticModulus": cone_resistance * elastic_modulus_factor,
"poissonRatio": poisson_ratio,
"unitWeight": unit_weight,
},
"prediction": {
"hystereticDampingBarkan": hysteretic_damping_barkan,
"measurementType": measurement_type,
},
"validation": {"sourceLocation": mapping(location)},
}
return payload
[docs]def create_cur166_payload(
buildings: gpd.GeoDataFrame,
location: Polygon | LineString | Point,
force: float,
reduction: float = 0,
installation_type: Literal["vibrate", "driving"] = "vibrate",
building_part: Literal["floor", "wall"] = "floor",
safety_factor: float = 0.05,
vibration_direction: Literal["vertical", "horizontal"] = "vertical",
frequency: float = 30.0,
vibration_type: Literal[
"short-term", "repeated-short-term", "continuous"
] = "continuous",
frequency_vibration_sensitive: float = 40.0,
reference_location: Literal[
"Amsterdam",
"Maasvlakte",
"Rotterdam",
"Groningen",
"Den Haag",
"Tiel",
"Eindhoven",
] = "Amsterdam",
measurement_type: Literal["indicative", "limited", "extensive"] = "extensive",
methode_safety_factor: Literal["CUR", "exact"] = "exact",
) -> dict:
"""
Create payload for VibraCore call `cur166/validation/multi`
Parameters
----------
buildings
GeoDataFrame that holds teh building information. Must have the following columns:
- foundationElement
Based on CUR 166 3rd edition table 5.19
- material
Based on CUR 166 3rd edition table 5.20
- category
Based on the SBR A table 10.1.
- monumental
Has the building structure a monumental status. Based on the SBR A table 10.3.
- structuralCondition
Based on the SBR A table 10.2.
- thickness
Layer thickness settlement-sensitive layer [m]
- vibrationSensitive
Has the building structure a vibration sensitive foundation. Based on the SBR A chapter 10.2.5.
location:
Location of the source
force:
Impact force of the pile [kN]
reduction:
Reduction of impact [%]
installation_type
Based on CUR 166 3rd edition table 5.20 or 5.21
building_part
Based on CUR 166 3rd edition table 5.20 or 5.21
safety_factor
Based on CUR 166 3rd edition table 5.22
vibration_direction
Based on CUR 166 3rd edition table 5.22
frequency:
The dominate frequency [Hz]
vibration_type
Based on the SBR A table 10.4.
frequency_vibration_sensitive
The dominate frequency for vibration sensitive building [Hz].
If not provided the frequency of 20 Hz is used for PrePal and 40
for CUR 166 3rd edition high frequency vibration calculation.
reference_location
Based on CUR 166-1997 table 5.16 and 5.17
measurement_type
Type of measurement based on the SBR A table 9.2.
methode_safety_factor
Parameter that indicated how the safety factor is calculated.
Find more info about the exact method here -> https://issuu.com/uitgeverijeducom/docs/geo_okt2014_totaal_v4_klein/36
Returns
-------
payload: dict
Raises
-------
ValueError:
No reference values found for reference location
KeyError:
Missing column names in GeoDataFrame
"""
reference = next(
(
item
for item in SOIL_REFERENCE
if item["location"] == reference_location
and item["method"] == installation_type
and item["vibration_direction"] == vibration_direction
),
None,
)
if reference is None:
raise ValueError(
f"No reference values found for reference location: {reference_location}"
f"with installation type: {installation_type} and vibration direction: {vibration_direction}."
)
columns = [
"foundationElement",
"material",
"category",
"monumental",
"structuralCondition",
"thickness",
"vibrationSensitive",
]
if not set(columns).issubset(set(buildings.columns)):
msg = (
f"Column names:{list(set(columns) - set(buildings.columns))} must be in GeoDataFrame. "
f"Found column names: {buildings.columns}"
)
raise KeyError(msg)
payload = {
"buildingInformation": [
{
"geometry": mapping(row.geometry),
"metadata": {"ID": row.get("name", uuid.uuid4().__str__())},
"properties_CUR": {
"buildingPart": building_part,
"foundationElement": row["foundationElement"],
"installationType": installation_type,
"material": row["material"],
"safetyFactor": safety_factor,
"vibrationDirection": vibration_direction,
},
"properties_SBRa": {
"category": row["category"],
"frequency": frequency,
"frequencyVibrationSensitive": frequency_vibration_sensitive,
"monumental": row["monumental"],
"structuralCondition": row["structuralCondition"],
"thickness": row["thickness"],
"vibrationSensitive": row["vibrationSensitive"],
"vibrationType": vibration_type,
},
}
for _, row in buildings.iterrows()
],
"prediction": {
"hystereticDampingBarkan": reference["alpha"],
"force": force * (100 - reduction) / 100,
"measurementType": measurement_type,
"methodeSafetyFactor": methode_safety_factor,
"referencesVelocity": reference["Uo"],
"variationCoefficient": reference["Vo"],
},
"validation": {"sourceLocation": mapping(location)},
}
return payload
[docs]def get_normative_building(
buildings: gpd.GeoDataFrame,
location: Polygon | LineString | Point,
category: Literal["one", "two"],
) -> str | None:
"""
Get the name of the closest building
Parameters
----------
buildings:
GeoDataFrame that holds the building information
location:
Geometry of the source location
category:
building category based on the SBR A table 10.1.
Returns
-------
name: str
"""
gdf = buildings.get(buildings["category"] == category)
if gdf.empty:
logging.error(f"ValueError: No buildings with category {category}.")
return None
gdf["distance"] = gdf.distance(location)
return gdf.sort_values("distance", na_position="last").iloc[0].get("name")
[docs]def create_single_payload(
multi_vibration_payload: dict,
name: str,
) -> dict:
"""
Create payload for VibraCore call `cur166/validation/single` or `prepal/validation/single`
Parameters
----------
multi_vibration_payload:
result from `create_cur166_payload` or `create_prepal_payload`
name:
building name
Returns
-------
payload: dict
"""
payload = deepcopy(multi_vibration_payload)
props = next(
(
item
for item in payload["buildingInformation"]
if item["metadata"]["ID"] == name
),
None,
)
if props is None:
raise ValueError(f"{name} is not a valid building name.")
payload.update(dict(buildingInformation=props))
return payload
[docs]def create_vibration_report_payload(
multi_vibration_payload: dict,
project_name: str,
project_id: str,
author: str,
) -> dict:
"""
Creates a dictionary with the payload content for the VibraCore endpoint
"/cur166/report" or "/prepal/report"
This dictionary can be passed directly to `nuclei.client.call_endpoint()`.
Parameters
----------
vibration_payload:
The result of a call to `create_cur166_payload()` or `create_prepal_payload()`
project_name:
The name of the project.
project_id:
The identifier (code) of the project.
author:
The author of the report.
Returns
-------
payload: dict
"""
payload = deepcopy(multi_vibration_payload)
payload.update(
dict(
reportProperties=dict(
author=author,
projectNumber=project_id,
projectName=project_name,
),
)
)
return payload