overturetoosm.objects
Pydantic models needed throughout the project.
1"""Pydantic models needed throughout the project.""" 2 3# ruff: noqa: D415 4 5from enum import Enum 6 7try: 8 from typing import Annotated 9except ImportError: 10 from typing import Annotated 11 12from pydantic import BaseModel, ConfigDict, Field, RootModel, field_validator 13 14from .resources import places_tags 15 16 17class OvertureBaseModel(BaseModel): 18 """Base model for Overture features.""" 19 20 model_config = ConfigDict(extra="forbid") 21 22 version: int = Field(ge=0) 23 theme: str | None = None 24 type: str | None = None 25 id: str | None = Field(None, pattern=r"^(\S.*)?\S$") 26 27 28class Wikidata(RootModel): 29 """Model for transportation segment wikidata.""" 30 31 root: str = Field(description="Wikidata ID.", pattern=r"^Q\d+") 32 33 34class Sources(BaseModel): 35 """Overture sources model.""" 36 37 property: str 38 dataset: str 39 record_id: str | None = None 40 confidence: float | None = Field(ge=0.0, le=1.0) 41 update_time: str | None = None 42 43 @field_validator("confidence") 44 @classmethod 45 def set_default_if_none(cls, v: float) -> float: 46 """@private""" 47 return v if v is not None else 0.0 48 49 def get_osm_link(self) -> str | None: 50 """Return the OSM link for the source.""" 51 if ( 52 self.record_id 53 and self.record_id.startswith(("n", "w", "r")) 54 and self.dataset == "OpenStreetMap" 55 ): 56 type_dict = {"n": "node", "w": "way", "r": "relation"} 57 return f"https://www.openstreetmap.org/{type_dict[self.record_id[0]]}/{self.record_id[1:]}" 58 59 60class RulesVariant(str, Enum): 61 """Overture name rules variant model.""" 62 63 alternate = "alternate" 64 common = "common" 65 official = "official" 66 short = "short" 67 68 69class Between(RootModel): 70 """Model for transportation segment between.""" 71 72 root: Annotated[list, Field(float, min_length=2, max_length=2)] 73 74 75class Mode(str, Enum): 76 """Model for political perspectives from which a named feature is viewed.""" 77 78 accepted_by = "accepted_by" 79 disputed_by = "disputed_by" 80 81 82class Perspectives(BaseModel): 83 """Model for political perspectives from which a named feature is viewed.""" 84 85 mode: Mode 86 countries: list[str] = Field(min_length=1) 87 88 89class Rules(BaseModel): 90 """Overture name rules model.""" 91 92 variant: RulesVariant 93 language: str | None = None 94 value: str 95 between: Between | None = None 96 side: str | None = None 97 perspectives: Perspectives | None = None 98 99 100class Names(BaseModel): 101 """Overture names model.""" 102 103 primary: str 104 common: list[tuple[str, str]] | None 105 rules: list[Rules] | None 106 107 def to_osm(self) -> dict[str, str]: 108 """Convert names to OSM tags.""" 109 names = {} 110 if self.primary: 111 names["name"] = self.primary 112 113 return names 114 115 116class PlaceAddress(BaseModel): 117 """Overture addresses model.""" 118 119 freeform: str | None 120 locality: str | None 121 postcode: str | None 122 region: str | None 123 country: str | None = Field(pattern=r"^[A-Z]{2}$") 124 125 def to_osm(self, region_tag: str) -> dict[str, str]: 126 """Convert address to OSM tags.""" 127 address_info = {} 128 if self.freeform: 129 address_info["addr:full"] = self.freeform 130 if self.country: 131 address_info["addr:country"] = self.country 132 if self.postcode: 133 address_info["addr:postcode"] = self.postcode 134 if self.locality: 135 address_info["addr:city"] = self.locality 136 if self.region: 137 address_info[region_tag] = self.region 138 139 return address_info 140 141 142class Categories(BaseModel): 143 """Overture categories model.""" 144 145 primary: str 146 alternate: list[str] | None 147 148 def to_osm(self, unmatched: str) -> dict[str, str]: 149 """Convert categories to OSM tags.""" 150 prim = places_tags.get(self.primary) 151 if prim: 152 return prim 153 elif unmatched == "force": 154 return {"type": self.primary} 155 elif unmatched == "error": 156 raise UnmatchedError(self.primary) 157 return {} 158 159 160class Brand(BaseModel): 161 """Overture brand model.""" 162 163 wikidata: Wikidata | None = None 164 names: Names 165 166 def to_osm(self) -> dict[str, str]: 167 """Convert brand properties to OSM tags.""" 168 osm = {"brand": self.names.primary} 169 if self.wikidata: 170 osm.update({"brand:wikidata": str(self.wikidata.root)}) 171 return osm 172 173 174class Socials(RootModel): 175 """Overture socials model.""" 176 177 root: list[str] 178 179 def to_osm(self) -> dict[str, str]: 180 """Convert socials properties to OSM tags.""" 181 new_props = {} 182 for social in self.root: 183 if "facebook" in social: 184 new_props["contact:facebook"] = social 185 elif "twitter" in str(social): 186 new_props["contact:twitter"] = social 187 return new_props 188 189 190class PlaceProps(OvertureBaseModel): 191 """Overture properties model. 192 193 Use this model directly if you want to manipulate the `place` properties yourself. 194 """ 195 196 model_config = ConfigDict(extra="ignore") 197 198 sources: list[Sources] 199 names: Names 200 brand: Brand | None = None 201 categories: Categories | None = None 202 confidence: float = Field(ge=0.0, le=1.0) 203 websites: list[str | None] | None = None 204 socials: Socials | None = None 205 emails: list[str | None] | None = None 206 phones: list[str | None] | None = None 207 addresses: list[PlaceAddress] 208 209 def to_osm( 210 self, confidence: float, region_tag: str, unmatched: str 211 ) -> dict[str, str]: 212 """Convert Overture's place properties to OSM tags. 213 214 Used internally by the `overturetoosm.process_place` function. 215 """ 216 if self.confidence < confidence: 217 raise ConfidenceError(confidence, self.confidence) 218 219 new_props = {} 220 221 # Categories 222 if self.categories: 223 new_props.update(self.categories.to_osm(unmatched)) 224 225 # Names 226 if self.names: 227 new_props.update(self.names.to_osm()) 228 229 # Contact information 230 new_props.update(self._process_contact_info()) 231 232 # Addresses 233 if self.addresses: 234 new_props.update(self.addresses[0].to_osm(region_tag)) 235 236 # Sources 237 new_props["source"] = source_statement(self.sources) 238 239 # Socials and Brand 240 if self.socials: 241 new_props.update(self.socials.to_osm()) 242 if self.brand: 243 new_props.update(self.brand.to_osm()) 244 245 return new_props 246 247 def _process_contact_info(self) -> dict[str, str]: 248 """Process contact information.""" 249 contact_info = {} 250 if not is_none_or_list_of_nones(self.phones): 251 contact_info["phone"] = self.phones[0] 252 if not is_none_or_list_of_nones(self.websites): 253 contact_info["website"] = str(self.websites[0]) 254 if not is_none_or_list_of_nones(self.emails): 255 contact_info["email"] = self.emails[0] 256 return contact_info 257 258 259class ConfidenceError(Exception): 260 """Confidence error exception. 261 262 This exception is raised when the confidence level of an item is below the 263 user-defined level. It contains the original confidence level and the confidence 264 level of the item. 265 266 Attributes: 267 confidence_level (float): The set confidence level. 268 confidence_item (float): The confidence of the item. 269 message (str): The error message. 270 """ 271 272 def __init__( 273 self, 274 confidence_level: float, 275 confidence_item: float, 276 message: str = "Confidence in this item is too low.", 277 ) -> None: 278 """@private""" 279 self.confidence_level = confidence_level 280 self.confidence_item = confidence_item 281 self.message = message 282 super().__init__(message) 283 284 def __str__(self) -> str: 285 """@private""" 286 lev = f"confidence_level={self.confidence_level}" 287 item = f"confidence_item={self.confidence_item}" 288 return f"""{self.message} {lev}, {item}""" 289 290 291class UnmatchedError(Exception): 292 """Unmatched category error. 293 294 This exception is raised when an item's Overture category does not have a 295 corresponding OSM definition. Edit 296 [the OSM Wiki page](https://wiki.openstreetmap.org/wiki/Overture_categories) 297 to add a definition to this category. 298 299 Attributes: 300 category (str): The Overture category that is unmatched. 301 message (str): The error message. 302 """ 303 304 def __init__( 305 self, category: str, message: str = "Overture category is unmatched." 306 ) -> None: 307 """@private""" 308 self.category = category 309 self.message = message 310 super().__init__(message) 311 312 def __str__(self) -> str: 313 """@private""" 314 return f"{self.message} {{category={self.category}}}" 315 316 317class BuildingProps(OvertureBaseModel): 318 """Overture building properties. 319 320 Use this model if you want to manipulate the `building` properties yourself. 321 """ 322 323 has_parts: bool 324 sources: list[Sources] 325 class_: str | None = Field(alias="class", default=None) 326 subtype: str | None = None 327 names: Names | None = None 328 level: int | None = None 329 height: float | None = None 330 is_underground: bool | None = None 331 num_floors: int | None = Field(serialization_alias="building:levels", default=None) 332 num_floors_underground: int | None = Field( 333 serialization_alias="building:levels:underground", default=None 334 ) 335 min_height: float | None = None 336 min_floor: int | None = Field( 337 serialization_alias="building:min_level", default=None 338 ) 339 facade_color: str | None = Field( 340 serialization_alias="building:colour", default=None 341 ) 342 facade_material: str | None = Field( 343 serialization_alias="building:material", default=None 344 ) 345 roof_material: str | None = Field(serialization_alias="roof:material", default=None) 346 roof_shape: str | None = Field(serialization_alias="roof:shape", default=None) 347 roof_direction: str | None = Field( 348 serialization_alias="roof:direction", default=None 349 ) 350 roof_orientation: str | None = Field( 351 serialization_alias="roof:orientation", default=None 352 ) 353 roof_color: str | None = Field(serialization_alias="roof:colour", default=None) 354 roof_height: float | None = Field(serialization_alias="roof:height", default=None) 355 356 def to_osm(self, confidence: float) -> dict[str, str]: 357 """Convert properties to OSM tags. 358 359 Used internally by`overturetoosm.process_building` function. 360 """ 361 new_props = {} 362 confidences = {source.confidence for source in self.sources} 363 if any(conf and conf < confidence for conf in confidences): 364 raise ConfidenceError(confidence, max({i for i in confidences if i})) 365 366 new_props["building"] = self.class_ if self.class_ else "yes" 367 368 new_props["source"] = source_statement(self.sources) 369 370 prop_obj = self.model_dump(exclude_none=True, by_alias=True).items() 371 new_props.update( 372 {k: v for k, v in prop_obj if k.startswith(("roof", "building"))} 373 ) 374 new_props.update({k: round(v, 2) for k, v in prop_obj if k.endswith("height")}) 375 376 if self.is_underground: 377 new_props["location"] = "underground" 378 if self.names: 379 new_props["name"] = self.names.primary 380 return new_props 381 382 383class AddressLevel(BaseModel): 384 """Overture address level model.""" 385 386 value: str 387 388 389class AddressProps(OvertureBaseModel): 390 """Overture address properties. 391 392 Use this model directly if you want to manipulate the `address` properties yourself. 393 """ 394 395 number: str | None = Field(serialization_alias="addr:housenumber", default=None) 396 street: str | None = Field(serialization_alias="addr:street", default=None) 397 unit: str | None = Field(serialization_alias="addr:unit", default=None) 398 postcode: str | None = Field(serialization_alias="addr:postcode", default=None) 399 postal_city: str | None = Field(serialization_alias="addr:city", default=None) 400 country: str | None = Field(serialization_alias="addr:country", default=None) 401 address_levels: ( 402 None | (Annotated[list[AddressLevel], Field(min_length=1, max_length=5)]) 403 ) = Field(default_factory=list) 404 sources: list[Sources] 405 406 def to_osm(self, style: str) -> dict[str, str]: 407 """Convert properties to OSM tags. 408 409 Used internally by `overturetoosm.process_address`. 410 """ 411 obj_dict = { 412 k: v 413 for k, v in self.model_dump(exclude_none=True, by_alias=True).items() 414 if k.startswith("addr:") 415 } 416 obj_dict["source"] = source_statement(self.sources) 417 418 if self.address_levels and len(self.address_levels) > 0 and style == "US": 419 obj_dict["addr:state"] = str(self.address_levels[0].value) 420 421 return obj_dict 422 423 424def source_statement(source: list[Sources]) -> str: 425 """Return a source statement from a list of sources.""" 426 return ( 427 ", ".join(sorted({i.dataset.strip(", ") for i in source})) 428 + " via overturetoosm" 429 ) 430 431 432def is_none_or_list_of_nones(value) -> bool: 433 """Check whether a given value is either None or a list containing only None values. 434 435 Args: 436 value: The value to check. Can be of any type. 437 438 Returns: 439 bool: True if the value is None or a list containing only None values, 440 False otherwise. 441 """ 442 if value is None: 443 return True 444 445 if isinstance(value, list): 446 return len(value) > 0 and all(item is None for item in value) 447 448 return False
18class OvertureBaseModel(BaseModel): 19 """Base model for Overture features.""" 20 21 model_config = ConfigDict(extra="forbid") 22 23 version: int = Field(ge=0) 24 theme: str | None = None 25 type: str | None = None 26 id: str | None = Field(None, pattern=r"^(\S.*)?\S$")
Base model for Overture features.
29class Wikidata(RootModel): 30 """Model for transportation segment wikidata.""" 31 32 root: str = Field(description="Wikidata ID.", pattern=r"^Q\d+")
Model for transportation segment wikidata.
35class Sources(BaseModel): 36 """Overture sources model.""" 37 38 property: str 39 dataset: str 40 record_id: str | None = None 41 confidence: float | None = Field(ge=0.0, le=1.0) 42 update_time: str | None = None 43 44 @field_validator("confidence") 45 @classmethod 46 def set_default_if_none(cls, v: float) -> float: 47 """@private""" 48 return v if v is not None else 0.0 49 50 def get_osm_link(self) -> str | None: 51 """Return the OSM link for the source.""" 52 if ( 53 self.record_id 54 and self.record_id.startswith(("n", "w", "r")) 55 and self.dataset == "OpenStreetMap" 56 ): 57 type_dict = {"n": "node", "w": "way", "r": "relation"} 58 return f"https://www.openstreetmap.org/{type_dict[self.record_id[0]]}/{self.record_id[1:]}"
Overture sources model.
50 def get_osm_link(self) -> str | None: 51 """Return the OSM link for the source.""" 52 if ( 53 self.record_id 54 and self.record_id.startswith(("n", "w", "r")) 55 and self.dataset == "OpenStreetMap" 56 ): 57 type_dict = {"n": "node", "w": "way", "r": "relation"} 58 return f"https://www.openstreetmap.org/{type_dict[self.record_id[0]]}/{self.record_id[1:]}"
Return the OSM link for the source.
61class RulesVariant(str, Enum): 62 """Overture name rules variant model.""" 63 64 alternate = "alternate" 65 common = "common" 66 official = "official" 67 short = "short"
Overture name rules variant model.
70class Between(RootModel): 71 """Model for transportation segment between.""" 72 73 root: Annotated[list, Field(float, min_length=2, max_length=2)]
Model for transportation segment between.
76class Mode(str, Enum): 77 """Model for political perspectives from which a named feature is viewed.""" 78 79 accepted_by = "accepted_by" 80 disputed_by = "disputed_by"
Model for political perspectives from which a named feature is viewed.
83class Perspectives(BaseModel): 84 """Model for political perspectives from which a named feature is viewed.""" 85 86 mode: Mode 87 countries: list[str] = Field(min_length=1)
Model for political perspectives from which a named feature is viewed.
90class Rules(BaseModel): 91 """Overture name rules model.""" 92 93 variant: RulesVariant 94 language: str | None = None 95 value: str 96 between: Between | None = None 97 side: str | None = None 98 perspectives: Perspectives | None = None
Overture name rules model.
101class Names(BaseModel): 102 """Overture names model.""" 103 104 primary: str 105 common: list[tuple[str, str]] | None 106 rules: list[Rules] | None 107 108 def to_osm(self) -> dict[str, str]: 109 """Convert names to OSM tags.""" 110 names = {} 111 if self.primary: 112 names["name"] = self.primary 113 114 return names
Overture names model.
117class PlaceAddress(BaseModel): 118 """Overture addresses model.""" 119 120 freeform: str | None 121 locality: str | None 122 postcode: str | None 123 region: str | None 124 country: str | None = Field(pattern=r"^[A-Z]{2}$") 125 126 def to_osm(self, region_tag: str) -> dict[str, str]: 127 """Convert address to OSM tags.""" 128 address_info = {} 129 if self.freeform: 130 address_info["addr:full"] = self.freeform 131 if self.country: 132 address_info["addr:country"] = self.country 133 if self.postcode: 134 address_info["addr:postcode"] = self.postcode 135 if self.locality: 136 address_info["addr:city"] = self.locality 137 if self.region: 138 address_info[region_tag] = self.region 139 140 return address_info
Overture addresses model.
126 def to_osm(self, region_tag: str) -> dict[str, str]: 127 """Convert address to OSM tags.""" 128 address_info = {} 129 if self.freeform: 130 address_info["addr:full"] = self.freeform 131 if self.country: 132 address_info["addr:country"] = self.country 133 if self.postcode: 134 address_info["addr:postcode"] = self.postcode 135 if self.locality: 136 address_info["addr:city"] = self.locality 137 if self.region: 138 address_info[region_tag] = self.region 139 140 return address_info
Convert address to OSM tags.
143class Categories(BaseModel): 144 """Overture categories model.""" 145 146 primary: str 147 alternate: list[str] | None 148 149 def to_osm(self, unmatched: str) -> dict[str, str]: 150 """Convert categories to OSM tags.""" 151 prim = places_tags.get(self.primary) 152 if prim: 153 return prim 154 elif unmatched == "force": 155 return {"type": self.primary} 156 elif unmatched == "error": 157 raise UnmatchedError(self.primary) 158 return {}
Overture categories model.
149 def to_osm(self, unmatched: str) -> dict[str, str]: 150 """Convert categories to OSM tags.""" 151 prim = places_tags.get(self.primary) 152 if prim: 153 return prim 154 elif unmatched == "force": 155 return {"type": self.primary} 156 elif unmatched == "error": 157 raise UnmatchedError(self.primary) 158 return {}
Convert categories to OSM tags.
161class Brand(BaseModel): 162 """Overture brand model.""" 163 164 wikidata: Wikidata | None = None 165 names: Names 166 167 def to_osm(self) -> dict[str, str]: 168 """Convert brand properties to OSM tags.""" 169 osm = {"brand": self.names.primary} 170 if self.wikidata: 171 osm.update({"brand:wikidata": str(self.wikidata.root)}) 172 return osm
Overture brand model.
175class Socials(RootModel): 176 """Overture socials model.""" 177 178 root: list[str] 179 180 def to_osm(self) -> dict[str, str]: 181 """Convert socials properties to OSM tags.""" 182 new_props = {} 183 for social in self.root: 184 if "facebook" in social: 185 new_props["contact:facebook"] = social 186 elif "twitter" in str(social): 187 new_props["contact:twitter"] = social 188 return new_props
Overture socials model.
180 def to_osm(self) -> dict[str, str]: 181 """Convert socials properties to OSM tags.""" 182 new_props = {} 183 for social in self.root: 184 if "facebook" in social: 185 new_props["contact:facebook"] = social 186 elif "twitter" in str(social): 187 new_props["contact:twitter"] = social 188 return new_props
Convert socials properties to OSM tags.
191class PlaceProps(OvertureBaseModel): 192 """Overture properties model. 193 194 Use this model directly if you want to manipulate the `place` properties yourself. 195 """ 196 197 model_config = ConfigDict(extra="ignore") 198 199 sources: list[Sources] 200 names: Names 201 brand: Brand | None = None 202 categories: Categories | None = None 203 confidence: float = Field(ge=0.0, le=1.0) 204 websites: list[str | None] | None = None 205 socials: Socials | None = None 206 emails: list[str | None] | None = None 207 phones: list[str | None] | None = None 208 addresses: list[PlaceAddress] 209 210 def to_osm( 211 self, confidence: float, region_tag: str, unmatched: str 212 ) -> dict[str, str]: 213 """Convert Overture's place properties to OSM tags. 214 215 Used internally by the `overturetoosm.process_place` function. 216 """ 217 if self.confidence < confidence: 218 raise ConfidenceError(confidence, self.confidence) 219 220 new_props = {} 221 222 # Categories 223 if self.categories: 224 new_props.update(self.categories.to_osm(unmatched)) 225 226 # Names 227 if self.names: 228 new_props.update(self.names.to_osm()) 229 230 # Contact information 231 new_props.update(self._process_contact_info()) 232 233 # Addresses 234 if self.addresses: 235 new_props.update(self.addresses[0].to_osm(region_tag)) 236 237 # Sources 238 new_props["source"] = source_statement(self.sources) 239 240 # Socials and Brand 241 if self.socials: 242 new_props.update(self.socials.to_osm()) 243 if self.brand: 244 new_props.update(self.brand.to_osm()) 245 246 return new_props 247 248 def _process_contact_info(self) -> dict[str, str]: 249 """Process contact information.""" 250 contact_info = {} 251 if not is_none_or_list_of_nones(self.phones): 252 contact_info["phone"] = self.phones[0] 253 if not is_none_or_list_of_nones(self.websites): 254 contact_info["website"] = str(self.websites[0]) 255 if not is_none_or_list_of_nones(self.emails): 256 contact_info["email"] = self.emails[0] 257 return contact_info
Overture properties model.
Use this model directly if you want to manipulate the place properties yourself.
210 def to_osm( 211 self, confidence: float, region_tag: str, unmatched: str 212 ) -> dict[str, str]: 213 """Convert Overture's place properties to OSM tags. 214 215 Used internally by the `overturetoosm.process_place` function. 216 """ 217 if self.confidence < confidence: 218 raise ConfidenceError(confidence, self.confidence) 219 220 new_props = {} 221 222 # Categories 223 if self.categories: 224 new_props.update(self.categories.to_osm(unmatched)) 225 226 # Names 227 if self.names: 228 new_props.update(self.names.to_osm()) 229 230 # Contact information 231 new_props.update(self._process_contact_info()) 232 233 # Addresses 234 if self.addresses: 235 new_props.update(self.addresses[0].to_osm(region_tag)) 236 237 # Sources 238 new_props["source"] = source_statement(self.sources) 239 240 # Socials and Brand 241 if self.socials: 242 new_props.update(self.socials.to_osm()) 243 if self.brand: 244 new_props.update(self.brand.to_osm()) 245 246 return new_props
Convert Overture's place properties to OSM tags.
Used internally by the overturetoosm.process_place function.
Inherited Members
260class ConfidenceError(Exception): 261 """Confidence error exception. 262 263 This exception is raised when the confidence level of an item is below the 264 user-defined level. It contains the original confidence level and the confidence 265 level of the item. 266 267 Attributes: 268 confidence_level (float): The set confidence level. 269 confidence_item (float): The confidence of the item. 270 message (str): The error message. 271 """ 272 273 def __init__( 274 self, 275 confidence_level: float, 276 confidence_item: float, 277 message: str = "Confidence in this item is too low.", 278 ) -> None: 279 """@private""" 280 self.confidence_level = confidence_level 281 self.confidence_item = confidence_item 282 self.message = message 283 super().__init__(message) 284 285 def __str__(self) -> str: 286 """@private""" 287 lev = f"confidence_level={self.confidence_level}" 288 item = f"confidence_item={self.confidence_item}" 289 return f"""{self.message} {lev}, {item}"""
Confidence error exception.
This exception is raised when the confidence level of an item is below the user-defined level. It contains the original confidence level and the confidence level of the item.
Attributes:
- confidence_level (float): The set confidence level.
- confidence_item (float): The confidence of the item.
- message (str): The error message.
292class UnmatchedError(Exception): 293 """Unmatched category error. 294 295 This exception is raised when an item's Overture category does not have a 296 corresponding OSM definition. Edit 297 [the OSM Wiki page](https://wiki.openstreetmap.org/wiki/Overture_categories) 298 to add a definition to this category. 299 300 Attributes: 301 category (str): The Overture category that is unmatched. 302 message (str): The error message. 303 """ 304 305 def __init__( 306 self, category: str, message: str = "Overture category is unmatched." 307 ) -> None: 308 """@private""" 309 self.category = category 310 self.message = message 311 super().__init__(message) 312 313 def __str__(self) -> str: 314 """@private""" 315 return f"{self.message} {{category={self.category}}}"
Unmatched category error.
This exception is raised when an item's Overture category does not have a corresponding OSM definition. Edit the OSM Wiki page to add a definition to this category.
Attributes:
- category (str): The Overture category that is unmatched.
- message (str): The error message.
318class BuildingProps(OvertureBaseModel): 319 """Overture building properties. 320 321 Use this model if you want to manipulate the `building` properties yourself. 322 """ 323 324 has_parts: bool 325 sources: list[Sources] 326 class_: str | None = Field(alias="class", default=None) 327 subtype: str | None = None 328 names: Names | None = None 329 level: int | None = None 330 height: float | None = None 331 is_underground: bool | None = None 332 num_floors: int | None = Field(serialization_alias="building:levels", default=None) 333 num_floors_underground: int | None = Field( 334 serialization_alias="building:levels:underground", default=None 335 ) 336 min_height: float | None = None 337 min_floor: int | None = Field( 338 serialization_alias="building:min_level", default=None 339 ) 340 facade_color: str | None = Field( 341 serialization_alias="building:colour", default=None 342 ) 343 facade_material: str | None = Field( 344 serialization_alias="building:material", default=None 345 ) 346 roof_material: str | None = Field(serialization_alias="roof:material", default=None) 347 roof_shape: str | None = Field(serialization_alias="roof:shape", default=None) 348 roof_direction: str | None = Field( 349 serialization_alias="roof:direction", default=None 350 ) 351 roof_orientation: str | None = Field( 352 serialization_alias="roof:orientation", default=None 353 ) 354 roof_color: str | None = Field(serialization_alias="roof:colour", default=None) 355 roof_height: float | None = Field(serialization_alias="roof:height", default=None) 356 357 def to_osm(self, confidence: float) -> dict[str, str]: 358 """Convert properties to OSM tags. 359 360 Used internally by`overturetoosm.process_building` function. 361 """ 362 new_props = {} 363 confidences = {source.confidence for source in self.sources} 364 if any(conf and conf < confidence for conf in confidences): 365 raise ConfidenceError(confidence, max({i for i in confidences if i})) 366 367 new_props["building"] = self.class_ if self.class_ else "yes" 368 369 new_props["source"] = source_statement(self.sources) 370 371 prop_obj = self.model_dump(exclude_none=True, by_alias=True).items() 372 new_props.update( 373 {k: v for k, v in prop_obj if k.startswith(("roof", "building"))} 374 ) 375 new_props.update({k: round(v, 2) for k, v in prop_obj if k.endswith("height")}) 376 377 if self.is_underground: 378 new_props["location"] = "underground" 379 if self.names: 380 new_props["name"] = self.names.primary 381 return new_props
Overture building properties.
Use this model if you want to manipulate the building properties yourself.
357 def to_osm(self, confidence: float) -> dict[str, str]: 358 """Convert properties to OSM tags. 359 360 Used internally by`overturetoosm.process_building` function. 361 """ 362 new_props = {} 363 confidences = {source.confidence for source in self.sources} 364 if any(conf and conf < confidence for conf in confidences): 365 raise ConfidenceError(confidence, max({i for i in confidences if i})) 366 367 new_props["building"] = self.class_ if self.class_ else "yes" 368 369 new_props["source"] = source_statement(self.sources) 370 371 prop_obj = self.model_dump(exclude_none=True, by_alias=True).items() 372 new_props.update( 373 {k: v for k, v in prop_obj if k.startswith(("roof", "building"))} 374 ) 375 new_props.update({k: round(v, 2) for k, v in prop_obj if k.endswith("height")}) 376 377 if self.is_underground: 378 new_props["location"] = "underground" 379 if self.names: 380 new_props["name"] = self.names.primary 381 return new_props
Convert properties to OSM tags.
Used internally byoverturetoosm.process_building function.
Inherited Members
Overture address level model.
390class AddressProps(OvertureBaseModel): 391 """Overture address properties. 392 393 Use this model directly if you want to manipulate the `address` properties yourself. 394 """ 395 396 number: str | None = Field(serialization_alias="addr:housenumber", default=None) 397 street: str | None = Field(serialization_alias="addr:street", default=None) 398 unit: str | None = Field(serialization_alias="addr:unit", default=None) 399 postcode: str | None = Field(serialization_alias="addr:postcode", default=None) 400 postal_city: str | None = Field(serialization_alias="addr:city", default=None) 401 country: str | None = Field(serialization_alias="addr:country", default=None) 402 address_levels: ( 403 None | (Annotated[list[AddressLevel], Field(min_length=1, max_length=5)]) 404 ) = Field(default_factory=list) 405 sources: list[Sources] 406 407 def to_osm(self, style: str) -> dict[str, str]: 408 """Convert properties to OSM tags. 409 410 Used internally by `overturetoosm.process_address`. 411 """ 412 obj_dict = { 413 k: v 414 for k, v in self.model_dump(exclude_none=True, by_alias=True).items() 415 if k.startswith("addr:") 416 } 417 obj_dict["source"] = source_statement(self.sources) 418 419 if self.address_levels and len(self.address_levels) > 0 and style == "US": 420 obj_dict["addr:state"] = str(self.address_levels[0].value) 421 422 return obj_dict
Overture address properties.
Use this model directly if you want to manipulate the address properties yourself.
407 def to_osm(self, style: str) -> dict[str, str]: 408 """Convert properties to OSM tags. 409 410 Used internally by `overturetoosm.process_address`. 411 """ 412 obj_dict = { 413 k: v 414 for k, v in self.model_dump(exclude_none=True, by_alias=True).items() 415 if k.startswith("addr:") 416 } 417 obj_dict["source"] = source_statement(self.sources) 418 419 if self.address_levels and len(self.address_levels) > 0 and style == "US": 420 obj_dict["addr:state"] = str(self.address_levels[0].value) 421 422 return obj_dict
Convert properties to OSM tags.
Used internally by overturetoosm.process_address.
Inherited Members
425def source_statement(source: list[Sources]) -> str: 426 """Return a source statement from a list of sources.""" 427 return ( 428 ", ".join(sorted({i.dataset.strip(", ") for i in source})) 429 + " via overturetoosm" 430 )
Return a source statement from a list of sources.
433def is_none_or_list_of_nones(value) -> bool: 434 """Check whether a given value is either None or a list containing only None values. 435 436 Args: 437 value: The value to check. Can be of any type. 438 439 Returns: 440 bool: True if the value is None or a list containing only None values, 441 False otherwise. 442 """ 443 if value is None: 444 return True 445 446 if isinstance(value, list): 447 return len(value) > 0 and all(item is None for item in value) 448 449 return False
Check whether a given value is either None or a list containing only None values.
Arguments:
- value: The value to check. Can be of any type.
Returns:
bool: True if the value is None or a list containing only None values, False otherwise.