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 Rules(BaseModel):
 76    """Overture name rules model."""
 77
 78    variant: RulesVariant
 79    language: str | None = None
 80    value: str
 81    between: Between | None = None
 82    side: str | None = None
 83
 84
 85class Names(BaseModel):
 86    """Overture names model."""
 87
 88    primary: str
 89    common: dict[str, str] | None
 90    rules: list[Rules] | None
 91
 92
 93class PlaceAddress(BaseModel):
 94    """Overture addresses model."""
 95
 96    freeform: str | None
 97    locality: str | None
 98    postcode: str | None
 99    region: str | None
100    country: str | None = Field(pattern=r"^[A-Z]{2}$")
101
102
103class Categories(BaseModel):
104    """Overture categories model."""
105
106    primary: str
107    alternate: list[str] | None
108
109
110class Brand(BaseModel):
111    """Overture brand model."""
112
113    wikidata: Wikidata | None = None
114    names: Names
115
116    def to_osm(self) -> dict[str, str]:
117        """Convert brand properties to OSM tags."""
118        osm = {"brand": self.names.primary}
119        if self.wikidata:
120            osm.update({"brand:wikidata": str(self.wikidata.root)})
121        return osm
122
123
124class Socials(RootModel):
125    """Overture socials model."""
126
127    root: list[str]
128
129    def to_osm(self) -> dict[str, str]:
130        """Convert socials properties to OSM tags."""
131        new_props = {}
132        for social in self.root:
133            if "facebook" in social:
134                new_props["contact:facebook"] = social
135            elif "twitter" in str(social):
136                new_props["contact:twitter"] = social
137        return new_props
138
139
140class PlaceProps(OvertureBaseModel):
141    """Overture properties model.
142
143    Use this model directly if you want to manipulate the `place` properties yourself.
144    """
145
146    sources: list[Sources]
147    names: Names
148    brand: Brand | None = None
149    categories: Categories | None = None
150    confidence: float = Field(ge=0.0, le=1.0)
151    websites: list[str] | None = None
152    socials: Socials | None = None
153    emails: list[str] | None = None
154    phones: list[str] | None = None
155    addresses: list[PlaceAddress]
156
157    def to_osm(
158        self, confidence: float, region_tag: str, unmatched: str
159    ) -> dict[str, str]:
160        """Convert Overture's place properties to OSM tags.
161
162        Used internally by the `overturetoosm.process_place` function.
163        """
164        new_props = {}
165        if self.confidence < confidence:
166            raise ConfidenceError(confidence, self.confidence)
167
168        if self.categories:
169            prim = places_tags.get(self.categories.primary)
170            if prim:
171                new_props = {**new_props, **prim}
172            elif unmatched == "force":
173                new_props["type"] = self.categories.primary
174            elif unmatched == "error":
175                raise UnmatchedError(self.categories.primary)
176
177        if self.names.primary:
178            new_props["name"] = self.names.primary
179
180        if self.phones is not None:
181            new_props["phone"] = self.phones[0]
182
183        if self.websites is not None and self.websites[0]:
184            new_props["website"] = str(self.websites[0])
185
186        if add := self.addresses[0]:
187            if add.freeform:
188                new_props["addr:street_address"] = add.freeform
189            if add.country:
190                new_props["addr:country"] = add.country
191            if add.postcode:
192                new_props["addr:postcode"] = add.postcode
193            if add.locality:
194                new_props["addr:city"] = add.locality
195            if add.region:
196                new_props[region_tag] = add.region
197
198        if self.sources:
199            new_props["source"] = source_statement(self.sources)
200
201        if self.socials:
202            new_props.update(self.socials.to_osm())
203
204        if self.brand:
205            new_props.update(self.brand.to_osm())
206
207        return new_props
208
209
210class ConfidenceError(Exception):
211    """Confidence error exception.
212
213    This exception is raised when the confidence level of an item is below the
214    user-defined level. It contains the original confidence level and the confidence
215    level of the item.
216
217    Attributes:
218        confidence_level (float): The set confidence level.
219        confidence_item (float): The confidence of the item.
220        message (str): The error message.
221    """
222
223    def __init__(
224        self,
225        confidence_level: float,
226        confidence_item: float,
227        message: str = "Confidence in this item is too low.",
228    ) -> None:
229        """@private"""
230        self.confidence_level = confidence_level
231        self.confidence_item = confidence_item
232        self.message = message
233        super().__init__(message)
234
235    def __str__(self) -> str:
236        """@private"""
237        lev = f"confidence_level={self.confidence_level}"
238        item = f"confidence_item={self.confidence_item}"
239        return f"""{self.message} {lev}, {item}"""
240
241
242class UnmatchedError(Exception):
243    """Unmatched category error.
244
245    This exception is raised when an item's Overture category does not have a
246    corresponding OSM definition. Edit
247    [the OSM Wiki page](https://wiki.openstreetmap.org/wiki/Overture_categories)
248    to add a definition to this category.
249
250    Attributes:
251        category (str): The Overture category that is unmatched.
252        message (str): The error message.
253    """
254
255    def __init__(
256        self, category: str, message: str = "Overture category is unmatched."
257    ) -> None:
258        """@private"""
259        self.category = category
260        self.message = message
261        super().__init__(message)
262
263    def __str__(self) -> str:
264        """@private"""
265        return f"{self.message} {{category={self.category}}}"
266
267
268class BuildingProps(OvertureBaseModel):
269    """Overture building properties.
270
271    Use this model if you want to manipulate the `building` properties yourself.
272    """
273
274    has_parts: bool
275    sources: list[Sources]
276    class_: str | None = Field(alias="class", default=None)
277    subtype: str | None = None
278    names: Names | None = None
279    level: int | None = None
280    height: float | None = None
281    is_underground: bool | None = None
282    num_floors: int | None = Field(serialization_alias="building:levels", default=None)
283    num_floors_underground: int | None = Field(
284        serialization_alias="building:levels:underground", default=None
285    )
286    min_height: float | None = None
287    min_floor: int | None = Field(
288        serialization_alias="building:min_level", default=None
289    )
290    facade_color: str | None = Field(
291        serialization_alias="building:colour", default=None
292    )
293    facade_material: str | None = Field(
294        serialization_alias="building:material", default=None
295    )
296    roof_material: str | None = Field(serialization_alias="roof:material", default=None)
297    roof_shape: str | None = Field(serialization_alias="roof:shape", default=None)
298    roof_direction: str | None = Field(
299        serialization_alias="roof:direction", default=None
300    )
301    roof_orientation: str | None = Field(
302        serialization_alias="roof:orientation", default=None
303    )
304    roof_color: str | None = Field(serialization_alias="roof:colour", default=None)
305    roof_height: float | None = Field(serialization_alias="roof:height", default=None)
306
307    def to_osm(self, confidence: float) -> dict[str, str]:
308        """Convert properties to OSM tags.
309
310        Used internally by`overturetoosm.process_building` function.
311        """
312        new_props = {}
313        confidences = {source.confidence for source in self.sources}
314        if any(conf and conf < confidence for conf in confidences):
315            raise ConfidenceError(confidence, max({i for i in confidences if i}))
316
317        new_props["building"] = self.class_ if self.class_ else "yes"
318
319        new_props["source"] = source_statement(self.sources)
320
321        prop_obj = self.model_dump(exclude_none=True, by_alias=True).items()
322        new_props.update(
323            {k: v for k, v in prop_obj if k.startswith(("roof", "building"))}
324        )
325        new_props.update({k: round(v, 2) for k, v in prop_obj if k.endswith("height")})
326
327        if self.is_underground:
328            new_props["location"] = "underground"
329        if self.names:
330            new_props["name"] = self.names.primary
331        return new_props
332
333
334class AddressLevel(BaseModel):
335    """Overture address level model."""
336
337    value: str
338
339
340class AddressProps(OvertureBaseModel):
341    """Overture address properties.
342
343    Use this model directly if you want to manipulate the `address` properties yourself.
344    """
345
346    number: str | None = Field(serialization_alias="addr:housenumber")
347    street: str | None = Field(serialization_alias="addr:street")
348    postcode: str | None = Field(serialization_alias="addr:postcode")
349    country: str | None = Field(serialization_alias="addr:country")
350    address_levels: (
351        None | (Annotated[list[AddressLevel], Field(min_length=1, max_length=5)])
352    ) = Field(default_factory=list)
353    sources: list[Sources]
354
355    def to_osm(self, style: str) -> dict[str, str]:
356        """Convert properties to OSM tags.
357
358        Used internally by `overturetoosm.process_address`.
359        """
360        obj_dict = {
361            k: v
362            for k, v in self.model_dump(exclude_none=True, by_alias=True).items()
363            if k.startswith("addr:")
364        }
365        obj_dict["source"] = source_statement(self.sources)
366
367        if self.address_levels and len(self.address_levels) > 0 and style == "US":
368            obj_dict["addr:state"] = str(self.address_levels[0].value)
369
370        return obj_dict
371
372
373def source_statement(source: list[Sources]) -> str:
374    """Return a source statement from a list of sources."""
375    return (
376        ", ".join(sorted({i.dataset.strip(", ") for i in source}))
377        + " via overturetoosm"
378    )
class OvertureBaseModel(pydantic.main.BaseModel):
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.

model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

version: int
theme: str | None
type: str | None
id: str | None
class Wikidata(pydantic.main.BaseModel, typing.Generic[~RootModelRootType]):
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.

root: str
class Sources(pydantic.main.BaseModel):
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.

property: str
dataset: str
record_id: str | None
confidence: float | None
update_time: str | None
model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class RulesVariant(builtins.str, enum.Enum):
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.

alternate = <RulesVariant.alternate: 'alternate'>
common = <RulesVariant.common: 'common'>
official = <RulesVariant.official: 'official'>
short = <RulesVariant.short: 'short'>
class Between(pydantic.main.BaseModel, typing.Generic[~RootModelRootType]):
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.

root: Annotated[list, FieldInfo(annotation=NoneType, required=False, default=<class 'float'>, metadata=[MinLen(min_length=2), MaxLen(max_length=2)])]
class Rules(pydantic.main.BaseModel):
76class Rules(BaseModel):
77    """Overture name rules model."""
78
79    variant: RulesVariant
80    language: str | None = None
81    value: str
82    between: Between | None = None
83    side: str | None = None

Overture name rules model.

variant: RulesVariant
language: str | None
value: str
between: Between | None
side: str | None
model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class Names(pydantic.main.BaseModel):
86class Names(BaseModel):
87    """Overture names model."""
88
89    primary: str
90    common: dict[str, str] | None
91    rules: list[Rules] | None

Overture names model.

primary: str
common: dict[str, str] | None
rules: list[Rules] | None
model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class PlaceAddress(pydantic.main.BaseModel):
 94class PlaceAddress(BaseModel):
 95    """Overture addresses model."""
 96
 97    freeform: str | None
 98    locality: str | None
 99    postcode: str | None
100    region: str | None
101    country: str | None = Field(pattern=r"^[A-Z]{2}$")

Overture addresses model.

freeform: str | None
locality: str | None
postcode: str | None
region: str | None
country: str | None
model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class Categories(pydantic.main.BaseModel):
104class Categories(BaseModel):
105    """Overture categories model."""
106
107    primary: str
108    alternate: list[str] | None

Overture categories model.

primary: str
alternate: list[str] | None
model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class Brand(pydantic.main.BaseModel):
111class Brand(BaseModel):
112    """Overture brand model."""
113
114    wikidata: Wikidata | None = None
115    names: Names
116
117    def to_osm(self) -> dict[str, str]:
118        """Convert brand properties to OSM tags."""
119        osm = {"brand": self.names.primary}
120        if self.wikidata:
121            osm.update({"brand:wikidata": str(self.wikidata.root)})
122        return osm

Overture brand model.

wikidata: Wikidata | None
names: Names
def to_osm(self) -> dict[str, str]:
117    def to_osm(self) -> dict[str, str]:
118        """Convert brand properties to OSM tags."""
119        osm = {"brand": self.names.primary}
120        if self.wikidata:
121            osm.update({"brand:wikidata": str(self.wikidata.root)})
122        return osm

Convert brand properties to OSM tags.

model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class Socials(pydantic.main.BaseModel, typing.Generic[~RootModelRootType]):
125class Socials(RootModel):
126    """Overture socials model."""
127
128    root: list[str]
129
130    def to_osm(self) -> dict[str, str]:
131        """Convert socials properties to OSM tags."""
132        new_props = {}
133        for social in self.root:
134            if "facebook" in social:
135                new_props["contact:facebook"] = social
136            elif "twitter" in str(social):
137                new_props["contact:twitter"] = social
138        return new_props

Overture socials model.

root: list[str]
def to_osm(self) -> dict[str, str]:
130    def to_osm(self) -> dict[str, str]:
131        """Convert socials properties to OSM tags."""
132        new_props = {}
133        for social in self.root:
134            if "facebook" in social:
135                new_props["contact:facebook"] = social
136            elif "twitter" in str(social):
137                new_props["contact:twitter"] = social
138        return new_props

Convert socials properties to OSM tags.

class PlaceProps(OvertureBaseModel):
141class PlaceProps(OvertureBaseModel):
142    """Overture properties model.
143
144    Use this model directly if you want to manipulate the `place` properties yourself.
145    """
146
147    sources: list[Sources]
148    names: Names
149    brand: Brand | None = None
150    categories: Categories | None = None
151    confidence: float = Field(ge=0.0, le=1.0)
152    websites: list[str] | None = None
153    socials: Socials | None = None
154    emails: list[str] | None = None
155    phones: list[str] | None = None
156    addresses: list[PlaceAddress]
157
158    def to_osm(
159        self, confidence: float, region_tag: str, unmatched: str
160    ) -> dict[str, str]:
161        """Convert Overture's place properties to OSM tags.
162
163        Used internally by the `overturetoosm.process_place` function.
164        """
165        new_props = {}
166        if self.confidence < confidence:
167            raise ConfidenceError(confidence, self.confidence)
168
169        if self.categories:
170            prim = places_tags.get(self.categories.primary)
171            if prim:
172                new_props = {**new_props, **prim}
173            elif unmatched == "force":
174                new_props["type"] = self.categories.primary
175            elif unmatched == "error":
176                raise UnmatchedError(self.categories.primary)
177
178        if self.names.primary:
179            new_props["name"] = self.names.primary
180
181        if self.phones is not None:
182            new_props["phone"] = self.phones[0]
183
184        if self.websites is not None and self.websites[0]:
185            new_props["website"] = str(self.websites[0])
186
187        if add := self.addresses[0]:
188            if add.freeform:
189                new_props["addr:street_address"] = add.freeform
190            if add.country:
191                new_props["addr:country"] = add.country
192            if add.postcode:
193                new_props["addr:postcode"] = add.postcode
194            if add.locality:
195                new_props["addr:city"] = add.locality
196            if add.region:
197                new_props[region_tag] = add.region
198
199        if self.sources:
200            new_props["source"] = source_statement(self.sources)
201
202        if self.socials:
203            new_props.update(self.socials.to_osm())
204
205        if self.brand:
206            new_props.update(self.brand.to_osm())
207
208        return new_props

Overture properties model.

Use this model directly if you want to manipulate the place properties yourself.

sources: list[Sources]
names: Names
brand: Brand | None
categories: Categories | None
confidence: float
websites: list[str] | None
socials: Socials | None
emails: list[str] | None
phones: list[str] | None
addresses: list[PlaceAddress]
def to_osm( self, confidence: float, region_tag: str, unmatched: str) -> dict[str, str]:
158    def to_osm(
159        self, confidence: float, region_tag: str, unmatched: str
160    ) -> dict[str, str]:
161        """Convert Overture's place properties to OSM tags.
162
163        Used internally by the `overturetoosm.process_place` function.
164        """
165        new_props = {}
166        if self.confidence < confidence:
167            raise ConfidenceError(confidence, self.confidence)
168
169        if self.categories:
170            prim = places_tags.get(self.categories.primary)
171            if prim:
172                new_props = {**new_props, **prim}
173            elif unmatched == "force":
174                new_props["type"] = self.categories.primary
175            elif unmatched == "error":
176                raise UnmatchedError(self.categories.primary)
177
178        if self.names.primary:
179            new_props["name"] = self.names.primary
180
181        if self.phones is not None:
182            new_props["phone"] = self.phones[0]
183
184        if self.websites is not None and self.websites[0]:
185            new_props["website"] = str(self.websites[0])
186
187        if add := self.addresses[0]:
188            if add.freeform:
189                new_props["addr:street_address"] = add.freeform
190            if add.country:
191                new_props["addr:country"] = add.country
192            if add.postcode:
193                new_props["addr:postcode"] = add.postcode
194            if add.locality:
195                new_props["addr:city"] = add.locality
196            if add.region:
197                new_props[region_tag] = add.region
198
199        if self.sources:
200            new_props["source"] = source_statement(self.sources)
201
202        if self.socials:
203            new_props.update(self.socials.to_osm())
204
205        if self.brand:
206            new_props.update(self.brand.to_osm())
207
208        return new_props

Convert Overture's place properties to OSM tags.

Used internally by the overturetoosm.process_place function.

model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Inherited Members
OvertureBaseModel
version
theme
type
id
class ConfidenceError(builtins.Exception):
211class ConfidenceError(Exception):
212    """Confidence error exception.
213
214    This exception is raised when the confidence level of an item is below the
215    user-defined level. It contains the original confidence level and the confidence
216    level of the item.
217
218    Attributes:
219        confidence_level (float): The set confidence level.
220        confidence_item (float): The confidence of the item.
221        message (str): The error message.
222    """
223
224    def __init__(
225        self,
226        confidence_level: float,
227        confidence_item: float,
228        message: str = "Confidence in this item is too low.",
229    ) -> None:
230        """@private"""
231        self.confidence_level = confidence_level
232        self.confidence_item = confidence_item
233        self.message = message
234        super().__init__(message)
235
236    def __str__(self) -> str:
237        """@private"""
238        lev = f"confidence_level={self.confidence_level}"
239        item = f"confidence_item={self.confidence_item}"
240        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.
confidence_level
confidence_item
message
class UnmatchedError(builtins.Exception):
243class UnmatchedError(Exception):
244    """Unmatched category error.
245
246    This exception is raised when an item's Overture category does not have a
247    corresponding OSM definition. Edit
248    [the OSM Wiki page](https://wiki.openstreetmap.org/wiki/Overture_categories)
249    to add a definition to this category.
250
251    Attributes:
252        category (str): The Overture category that is unmatched.
253        message (str): The error message.
254    """
255
256    def __init__(
257        self, category: str, message: str = "Overture category is unmatched."
258    ) -> None:
259        """@private"""
260        self.category = category
261        self.message = message
262        super().__init__(message)
263
264    def __str__(self) -> str:
265        """@private"""
266        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.
category
message
class BuildingProps(OvertureBaseModel):
269class BuildingProps(OvertureBaseModel):
270    """Overture building properties.
271
272    Use this model if you want to manipulate the `building` properties yourself.
273    """
274
275    has_parts: bool
276    sources: list[Sources]
277    class_: str | None = Field(alias="class", default=None)
278    subtype: str | None = None
279    names: Names | None = None
280    level: int | None = None
281    height: float | None = None
282    is_underground: bool | None = None
283    num_floors: int | None = Field(serialization_alias="building:levels", default=None)
284    num_floors_underground: int | None = Field(
285        serialization_alias="building:levels:underground", default=None
286    )
287    min_height: float | None = None
288    min_floor: int | None = Field(
289        serialization_alias="building:min_level", default=None
290    )
291    facade_color: str | None = Field(
292        serialization_alias="building:colour", default=None
293    )
294    facade_material: str | None = Field(
295        serialization_alias="building:material", default=None
296    )
297    roof_material: str | None = Field(serialization_alias="roof:material", default=None)
298    roof_shape: str | None = Field(serialization_alias="roof:shape", default=None)
299    roof_direction: str | None = Field(
300        serialization_alias="roof:direction", default=None
301    )
302    roof_orientation: str | None = Field(
303        serialization_alias="roof:orientation", default=None
304    )
305    roof_color: str | None = Field(serialization_alias="roof:colour", default=None)
306    roof_height: float | None = Field(serialization_alias="roof:height", default=None)
307
308    def to_osm(self, confidence: float) -> dict[str, str]:
309        """Convert properties to OSM tags.
310
311        Used internally by`overturetoosm.process_building` function.
312        """
313        new_props = {}
314        confidences = {source.confidence for source in self.sources}
315        if any(conf and conf < confidence for conf in confidences):
316            raise ConfidenceError(confidence, max({i for i in confidences if i}))
317
318        new_props["building"] = self.class_ if self.class_ else "yes"
319
320        new_props["source"] = source_statement(self.sources)
321
322        prop_obj = self.model_dump(exclude_none=True, by_alias=True).items()
323        new_props.update(
324            {k: v for k, v in prop_obj if k.startswith(("roof", "building"))}
325        )
326        new_props.update({k: round(v, 2) for k, v in prop_obj if k.endswith("height")})
327
328        if self.is_underground:
329            new_props["location"] = "underground"
330        if self.names:
331            new_props["name"] = self.names.primary
332        return new_props

Overture building properties.

Use this model if you want to manipulate the building properties yourself.

has_parts: bool
sources: list[Sources]
class_: str | None
subtype: str | None
names: Names | None
level: int | None
height: float | None
is_underground: bool | None
num_floors: int | None
num_floors_underground: int | None
min_height: float | None
min_floor: int | None
facade_color: str | None
facade_material: str | None
roof_material: str | None
roof_shape: str | None
roof_direction: str | None
roof_orientation: str | None
roof_color: str | None
roof_height: float | None
def to_osm(self, confidence: float) -> dict[str, str]:
308    def to_osm(self, confidence: float) -> dict[str, str]:
309        """Convert properties to OSM tags.
310
311        Used internally by`overturetoosm.process_building` function.
312        """
313        new_props = {}
314        confidences = {source.confidence for source in self.sources}
315        if any(conf and conf < confidence for conf in confidences):
316            raise ConfidenceError(confidence, max({i for i in confidences if i}))
317
318        new_props["building"] = self.class_ if self.class_ else "yes"
319
320        new_props["source"] = source_statement(self.sources)
321
322        prop_obj = self.model_dump(exclude_none=True, by_alias=True).items()
323        new_props.update(
324            {k: v for k, v in prop_obj if k.startswith(("roof", "building"))}
325        )
326        new_props.update({k: round(v, 2) for k, v in prop_obj if k.endswith("height")})
327
328        if self.is_underground:
329            new_props["location"] = "underground"
330        if self.names:
331            new_props["name"] = self.names.primary
332        return new_props

Convert properties to OSM tags.

Used internally byoverturetoosm.process_building function.

model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Inherited Members
OvertureBaseModel
version
theme
type
id
class AddressLevel(pydantic.main.BaseModel):
335class AddressLevel(BaseModel):
336    """Overture address level model."""
337
338    value: str

Overture address level model.

value: str
model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class AddressProps(OvertureBaseModel):
341class AddressProps(OvertureBaseModel):
342    """Overture address properties.
343
344    Use this model directly if you want to manipulate the `address` properties yourself.
345    """
346
347    number: str | None = Field(serialization_alias="addr:housenumber")
348    street: str | None = Field(serialization_alias="addr:street")
349    postcode: str | None = Field(serialization_alias="addr:postcode")
350    country: str | None = Field(serialization_alias="addr:country")
351    address_levels: (
352        None | (Annotated[list[AddressLevel], Field(min_length=1, max_length=5)])
353    ) = Field(default_factory=list)
354    sources: list[Sources]
355
356    def to_osm(self, style: str) -> dict[str, str]:
357        """Convert properties to OSM tags.
358
359        Used internally by `overturetoosm.process_address`.
360        """
361        obj_dict = {
362            k: v
363            for k, v in self.model_dump(exclude_none=True, by_alias=True).items()
364            if k.startswith("addr:")
365        }
366        obj_dict["source"] = source_statement(self.sources)
367
368        if self.address_levels and len(self.address_levels) > 0 and style == "US":
369            obj_dict["addr:state"] = str(self.address_levels[0].value)
370
371        return obj_dict

Overture address properties.

Use this model directly if you want to manipulate the address properties yourself.

number: str | None
street: str | None
postcode: str | None
country: str | None
address_levels: Optional[Annotated[list[AddressLevel], FieldInfo(annotation=NoneType, required=True, metadata=[MinLen(min_length=1), MaxLen(max_length=5)])]]
sources: list[Sources]
def to_osm(self, style: str) -> dict[str, str]:
356    def to_osm(self, style: str) -> dict[str, str]:
357        """Convert properties to OSM tags.
358
359        Used internally by `overturetoosm.process_address`.
360        """
361        obj_dict = {
362            k: v
363            for k, v in self.model_dump(exclude_none=True, by_alias=True).items()
364            if k.startswith("addr:")
365        }
366        obj_dict["source"] = source_statement(self.sources)
367
368        if self.address_levels and len(self.address_levels) > 0 and style == "US":
369            obj_dict["addr:state"] = str(self.address_levels[0].value)
370
371        return obj_dict

Convert properties to OSM tags.

Used internally by overturetoosm.process_address.

model_config = {'extra': 'forbid'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Inherited Members
OvertureBaseModel
version
theme
type
id
def source_statement(source: list[Sources]) -> str:
374def source_statement(source: list[Sources]) -> str:
375    """Return a source statement from a list of sources."""
376    return (
377        ", ".join(sorted({i.dataset.strip(", ") for i in source}))
378        + " via overturetoosm"
379    )

Return a source statement from a list of sources.