Source code for betty.ancestry.place

"""
Provide the place entity.
"""

from __future__ import annotations

from contextlib import suppress
from typing import final, MutableSequence, Iterable, Iterator, TYPE_CHECKING

from typing_extensions import override

from betty.ancestry.has_file_references import HasFileReferences
from betty.ancestry.has_notes import HasNotes
from betty.ancestry.link import HasLinks, Link
from betty.ancestry.name import Name
from betty.ancestry.place_type.place_types import Unknown as UnknownPlaceType
from betty.json.linked_data import dump_context, JsonLdObject
from betty.json.schema import Array, Number, Object
from betty.locale.localizable import _, Localizable
from betty.model import UserFacingEntity, Entity
from betty.model.association import BidirectionalToMany, ToManyResolver
from betty.plugin import ShorthandPluginBase
from betty.privacy import HasPrivacy

if TYPE_CHECKING:
    from betty.ancestry.note import Note
    from betty.ancestry.event import Event
    from betty.ancestry.enclosure import Enclosure
    from betty.ancestry.place_type import PlaceType
    from betty.privacy import Privacy
    from geopy import Point
    from betty.project import Project
    from betty.serde.dump import DumpMapping, Dump


[docs] @final class Place( ShorthandPluginBase, HasLinks, HasFileReferences, HasNotes, HasPrivacy, UserFacingEntity, Entity, ): """ A place. A place is a physical location on earth. It may be identifiable by GPS coordinates only, or be a well-known city, with names in many languages, imagery, and its own Wikipedia page, or any type of place in between. """ _plugin_id = "place" _plugin_label = _("Place") events = BidirectionalToMany["Place", "Event"]( "betty.ancestry.place:Place", "events", "betty.ancestry.event:Event", "place", title="Events", description="The events that happened in this place", ) enclosers = BidirectionalToMany["Place", "Enclosure"]( "betty.ancestry.place:Place", "encloser", "betty.ancestry.enclosure:Enclosure", "enclosee", title="Enclosers", description="The places this place is enclosed or contained by", linked_data_embedded=True, ) enclosees = BidirectionalToMany["Place", "Enclosure"]( "betty.ancestry.place:Place", "enclosee", "betty.ancestry.enclosure:Enclosure", "encloser", title="Enclosees", description="The places this place encloses or contains", linked_data_embedded=True, )
[docs] def __init__( self, *, id: str | None = None, # noqa A002 names: MutableSequence[Name] | None = None, events: Iterable[Event] | ToManyResolver[Event] | None = None, enclosers: Iterable["Enclosure"] | ToManyResolver["Enclosure"] | None = None, enclosees: Iterable["Enclosure"] | ToManyResolver["Enclosure"] | None = None, notes: Iterable[Note] | ToManyResolver[Note] | None = None, coordinates: Point | None = None, links: MutableSequence[Link] | None = None, privacy: Privacy | None = None, public: bool | None = None, private: bool | None = None, place_type: PlaceType | None = None, ): super().__init__( id, notes=notes, links=links, privacy=privacy, public=public, private=private, ) self._names = [] if names is None else names self._coordinates = coordinates if events is not None: self.events = events if enclosers is not None: self.enclosers = enclosers if enclosees is not None: self.enclosees = enclosees self._place_type = place_type or UnknownPlaceType()
@property def walk_enclosees(self) -> Iterator["Enclosure"]: """ All enclosed places. """ for enclosure in self.enclosees: yield enclosure yield from enclosure.enclosee.walk_enclosees
[docs] @override @classmethod def plugin_label_plural(cls) -> Localizable: return _("Places")
@property def place_type(self) -> PlaceType: """ The type of this place. """ return self._place_type @place_type.setter def place_type(self, place_type: PlaceType) -> None: self._place_type = place_type @property def names(self) -> MutableSequence[Name]: """ The place's names. The first name is considered the :py:attr:`place label <betty.ancestry.place.Place.label>`. """ return self._names @property def coordinates(self) -> Point | None: """ The place's coordinates. """ return self._coordinates @coordinates.setter def coordinates(self, coordinates: Point): self._coordinates = coordinates @override @property def label(self) -> Localizable: with suppress(IndexError): return self.names[0].name return super().label
[docs] @override async def dump_linked_data(self, project: Project) -> DumpMapping[Dump]: dump = await super().dump_linked_data(project) dump_context( dump, names="https://schema.org/name", events="https://schema.org/event", enclosers="https://schema.org/containedInPlace", enclosees="https://schema.org/containsPlace", ) dump["@type"] = "https://schema.org/Place" dump["names"] = [await name.dump_linked_data(project) for name in self.names] if self.coordinates is not None: dump["coordinates"] = { "@type": "https://schema.org/GeoCoordinates", "latitude": self.coordinates.latitude, "longitude": self.coordinates.longitude, } dump_context(dump, coordinates="https://schema.org/geo") dump_context( dump["coordinates"], # type: ignore[arg-type] latitude="https://schema.org/latitude", ) dump_context( dump["coordinates"], # type: ignore[arg-type] longitude="https://schema.org/longitude", ) return dump
[docs] @override @classmethod async def linked_data_schema(cls, project: Project) -> JsonLdObject: schema = await super().linked_data_schema(project) schema.add_property( "names", Array(await Name.linked_data_schema(project), title="Names") ) coordinate_schema = Number(title="Coordinate") coordinates_schema = Object(title="Coordinates") coordinates_schema.add_property("latitude", coordinate_schema, False) coordinates_schema.add_property("longitude", coordinate_schema, False) schema.add_property("coordinates", coordinates_schema, False) return schema