diff --git a/src/czml3/base.py b/src/czml3/base.py index d09890d..567177f 100644 --- a/src/czml3/base.py +++ b/src/czml3/base.py @@ -3,7 +3,8 @@ import warnings from enum import Enum from json import JSONEncoder -from typing import List + +import attr from .constants import ISO8601_FORMAT_Z @@ -24,9 +25,8 @@ def default(self, o): return super().default(o) +@attr.s(repr=False, frozen=True) class BaseCZMLObject: - KNOWN_PROPERTIES = [] # type: List[str] - def __repr__(self): return self.dumps(indent=4) @@ -45,7 +45,7 @@ def to_json(self): if getattr(self, "delete", False): properties_list = NON_DELETE_PROPERTIES else: - properties_list = self.KNOWN_PROPERTIES + properties_list = list(attr.asdict(self).keys()) obj_dict = {} for property_name in properties_list: diff --git a/src/czml3/common.py b/src/czml3/common.py index 30bd296..da9a344 100644 --- a/src/czml3/common.py +++ b/src/czml3/common.py @@ -7,21 +7,7 @@ class Deletable: """A property whose value may be deleted.""" - _delete: bool - - @property - def delete(self): - """ - Whether the client should delete existing samples or interval data for this property. - - Data will be deleted for the containing interval, - or if there is no containing interval, - then all data. - If true, - all other properties in this property - will be ignored. - """ - return self._delete + delete: bool # noinspection PyPep8Naming @@ -31,49 +17,14 @@ class Interpolatable: The interpolation happens over provided time-tagged samples. """ - _epoch: dt.datetime - _interpolation_algorithm: InterpolationAlgorithms - _interpolation_degree: int - - @property - def epoch(self): - """The epoch to use for times specified as seconds since an epoch.""" - return self._epoch - - @property - def interpolationAlgorithm(self): - """The interpolation algorithm to use when interpolating.""" - return self._interpolation_algorithm - - @property - def interpolationDegree(self): - """The degree of interpolation to use when interpolating.""" - return self._interpolation_degree + epoch: dt.datetime + interpolation_algorithm: InterpolationAlgorithms + interpolation_degree: int # noinspection PyPep8Naming class HasAlignment: """A property that can be horizontally or vertically aligned.""" - _horizontal_origin: HorizontalOrigins - _vertical_origin: VerticalOrigins - - @property - def horizontalOrigin(self): - """The horizontal origin of the object. - - It controls whether the object is - left-, center-, or right-aligned with the position. - - """ - return self._horizontal_origin - - @property - def verticalOrigin(self): - """The vertical origin of the object. - - Determines whether the object is - bottom-, center-, or top-aligned with the position. - - """ - return self._vertical_origin + horizontal_origin: HorizontalOrigins + vertical_origin: VerticalOrigins diff --git a/src/czml3/core.py b/src/czml3/core.py index cf5310d..8da3167 100644 --- a/src/czml3/core.py +++ b/src/czml3/core.py @@ -1,41 +1,27 @@ from uuid import uuid4 +import attr + from .base import BaseCZMLObject from .types import Sequence CZML_VERSION = "1.0" +@attr.s(repr=False, frozen=True, kw_only=True) class Preamble(BaseCZMLObject): """The preamble packet.""" - KNOWN_PROPERTIES = ["id", "version", "name", "clock"] - - def __init__(self, *, version=CZML_VERSION, name=None, clock=None): - self._id = "document" - self._version = version - self._name = name - self._clock = clock + version = attr.ib(default=CZML_VERSION) + name = attr.ib(default=None) + clock = attr.ib(default=None) @property def id(self): - return self._id - - @property - def version(self): - """The CZML version being written.""" - return self._version - - @property - def name(self): - return self._name - - @property - def clock(self): - """The clock settings for the entire data set.""" - return self._clock + return "document" +@attr.s(repr=False, frozen=True, kw_only=True) class Packet(BaseCZMLObject): """A CZML Packet. @@ -43,224 +29,25 @@ class Packet(BaseCZMLObject): for further information. """ - # https://github.com/AnalyticalGraphicsInc/czml-writer/wiki/Packet - KNOWN_PROPERTIES = [ - "id", - "delete", - "name", - "parent", - "description", - "availability", - "properties", - "position", - "orientation", - "viewFrom", - "billboard", - "box", - "corridor", - "cylinder", - "ellipse", - "ellipsoid", - "label", - "model", - "path", - "point", - "polygon", - "polyline", - "rectangle", - "wall", - ] - - def __init__( - self, - *, - id=None, - delete=None, - name=None, - parent=None, - description=None, - availability=None, - properties=None, - position=None, - orientation=None, - billboard=None, - label=None, - model=None, - path=None, - point=None, - polygon=None, - polyline=None, - ): - if id is None: - id = str(uuid4()) - - self._id = id - self._delete = delete - self._name = name - self._parent = parent - self._description = description - self._availability = availability - self._properties = properties - self._position = position - self._orientation = orientation - self._billboard = billboard - self._label = label - self._model = model - self._path = path - self._point = point - self._polygon = polygon - self._polyline = polyline - - @property - def id(self): - """The ID of the object described by this packet. - - IDs do not need to be GUIDs, - but they do need to uniquely identify a single object within a CZML source - and any other CZML sources loaded into the same scope. - If this property is not specified, - the client will automatically generate a unique one. - However, - this prevents later packets from referring to this object - in order to add more data to it. - """ - return self._id - - @property - def delete(self): - """Whether the client should delete all existing data for this object. - - If true, all other properties in this packet will be ignored. - """ - return self._delete - - @property - def name(self): - """The name of the object. - - It does not have to be unique and is intended for user consumption. - """ - return self._name - - @property - def parent(self): - """An HTML description of the object.""" - return self._parent - - @property - def description(self): - """An HTML description of the object.""" - return self._description - - @property - def availability(self): - """The set of time intervals over which data for an object is available. - - The property can be a single string specifying a single interval, - or an array of strings representing intervals. - A later CZML packet can update this availability - if it changes or is found to be incorrect. - For example, an SGP4 propagator may initially report availability for all time, - but then later the propagator throws an exception - and the availability can be adjusted to end at that time. - If this optional property is not present, - the object is assumed to be available for all time. - Availability is scoped to a particular CZML stream, - so two different streams can list different availability for a single object. - Within a single stream, - the last availability stated for an object is the one in effect - and any availabilities in previous packets are ignored. - If an object is not available at a time, - the client will not draw that object. - """ - return self._availability - - @property - def properties(self): - """A set of custom properties for this object.""" - return self._properties - - @property - def position(self): - """The position of the object in the world. - - The position has no direct visual representation, - but it is used to locate billboards, labels, - and other graphical items attached to the object. - """ - return self._position - - @property - def orientation(self): - """The orientation of the object in the world. - - The orientation has no direct visual representation, - but it is used to orient models, cones, pyramids, - and other graphical items attached to the object. - - """ - return self._orientation - - @property - def billboard(self): - """A billboard, or viewport-aligned image, sometimes called a marker. - - The billboard is positioned in the scene by the position property. - - """ - return self._billboard - - @property - def label(self): - """A string of text. - - The label is positioned in the scene by the position property. - - """ - return self._label - - @property - def model(self): - """A 3D model. - - The model is positioned and oriented using the position and orientation properties. - - """ - return self._model - - @property - def path(self): - """A path, which is a polyline defined by the motion of an object over time. - - The possible vertices of the path are specified by the position property. - - """ - return self._path - - @property - def point(self): - """A point, or viewport-aligned circle. - - The point is positioned in the scene by the position property. - - """ - return self._point - - @property - def polygon(self): - """A polygon, which is a closed figure on the surface of the Earth. - - """ - return self._polygon - - @property - def polyline(self): - """A polyline, which is a line in the scene composed of multiple segments. - - """ - return self._polyline - - + id = attr.ib(factory=lambda: str(uuid4())) + delete = attr.ib(default=None) + name = attr.ib(default=None) + parent = attr.ib(default=None) + description = attr.ib(default=None) + availability = attr.ib(default=None) + properties = attr.ib(default=None) + position = attr.ib(default=None) + orientation = attr.ib(default=None) + billboard = attr.ib(default=None) + label = attr.ib(default=None) + model = attr.ib(default=None) + path = attr.ib(default=None) + point = attr.ib(default=None) + polygon = attr.ib(default=None) + polyline = attr.ib(default=None) + + +@attr.s(repr=False, frozen=True) class Document(Sequence): """A CZML document, consisting on a list of packets.""" diff --git a/src/czml3/enums.py b/src/czml3/enums.py index 64aa0bd..ab1f418 100644 --- a/src/czml3/enums.py +++ b/src/czml3/enums.py @@ -68,3 +68,15 @@ class ShadowModes(Enum): ENABLED = auto() CAST_ONLY = auto() RECEIVE_ONLY = auto() + + +class ClassificationTypes(Enum): + TERRAIN = auto() + CESIUM_3D_TILE = auto() + BOTH = auto() + + +class ArcTypes(Enum): + NONE = auto() + GEODESIC = auto() + RHUMB = auto() diff --git a/src/czml3/examples/simple.py b/src/czml3/examples/simple.py index fd708b6..42dcdd8 100644 --- a/src/czml3/examples/simple.py +++ b/src/czml3/examples/simple.py @@ -8,14 +8,22 @@ ReferenceFrames, VerticalOrigins, ) -from czml3.properties import Billboard, Clock, Color, Label, Material, Path, Position +from czml3.properties import ( + Billboard, + Clock, + Color, + Label, + Material, + Path, + Position, + SolidColorMaterial, +) from czml3.types import IntervalValue, Sequence, TimeInterval accesses_id = "9927edc4-e87a-4e1f-9b8b-0bfb3b05b227" start = dt.datetime(2012, 3, 15, 10, tzinfo=dt.timezone.utc) end = dt.datetime(2012, 3, 16, 10, tzinfo=dt.timezone.utc) -# simple = Document( [ Preamble( @@ -94,8 +102,8 @@ outlineWidth=2, text="Pennsylvania", verticalOrigin=VerticalOrigins.CENTER, - fillColor=Color(rgba=[255, 0, 0, 255]), - outlineColor=Color(rgba=[0, 0, 0, 255]), + fillColor=Color.from_list([255, 0, 0]), + outlineColor=Color.from_list([0, 0, 0]), ), position=Position( cartesian=[1152255.80150063, -4694317.951340558, 4147335.9067563135] @@ -128,8 +136,8 @@ style=LabelStyles.FILL_AND_OUTLINE, text="AGI", verticalOrigin=VerticalOrigins.CENTER, - fillColor=Color(rgba=[0, 255, 255, 255]), - outlineColor=Color(rgba=[0, 0, 0, 255]), + fillColor=Color.from_list([0, 255, 255]), + outlineColor=Color.from_list([0, 0, 0]), ), position=Position( cartesian=[1216469.9357990976, -4736121.71856379, 4081386.8856866374] @@ -162,14 +170,14 @@ style=LabelStyles.FILL_AND_OUTLINE, text="Geoeye 1", verticalOrigin=VerticalOrigins.CENTER, - fillColor=Color(rgba=[0, 255, 0, 255]), - outlineColor=Color(rgba=[0, 0, 0, 255]), + fillColor=Color.from_list([0, 255, 0]), + outlineColor=Color.from_list([0, 0, 0]), ), path=Path( show=Sequence([IntervalValue(start=start, end=end, value=True)]), width=1, resolution=120, - material=Material(solidColor=Color(rgba=[0, 255, 0, 255])), + material=Material(solidColor=SolidColorMaterial.from_list([0, 255, 0])), ), position=Position( interpolationAlgorithm=InterpolationAlgorithms.LAGRANGE, diff --git a/src/czml3/properties.py b/src/czml3/properties.py index c92a8e6..55b28ee 100644 --- a/src/czml3/properties.py +++ b/src/czml3/properties.py @@ -1,426 +1,167 @@ +import attr +from w3lib.url import is_url, parse_data_uri + from .base import BaseCZMLObject from .common import Deletable, HasAlignment, Interpolatable from .enums import ClockRanges, ClockSteps, LabelStyles -from .types import ( - ArcTypeValue, - Cartesian3Value, - CartographicDegreesValue, - CartographicRadiansValue, - FontValue, - RgbafValue, - RgbaValue, - UnitQuaternionValue, - Uri, -) +from .types import RgbafValue, RgbaValue +@attr.s(repr=False, frozen=True, kw_only=True) class Material(BaseCZMLObject): """A definition of how a surface is colored or shaded.""" - KNOWN_PROPERTIES = ["solidColor", "image", "grid", "stripe", "checkerboard"] - - def __init__( - self, *, solidColor=None, image=None, grid=None, stripe=None, checkerboard=None - ): - if isinstance(solidColor, Color): - solidColor = SolidColorMaterial(color=solidColor) - - self._solid_color = solidColor - self._image = image - self._grid = grid - self._stripe = stripe - self._checkerboard = checkerboard - - @property - def solidColor(self): - """A material that fills the surface with a solid color, which may be translucent.""" - return self._solid_color - - @property - def image(self): - """A material that fills the surface with an image.""" - return self._image - - @property - def grid(self): - """A material that fills the surface with a grid.""" - return self._grid - - @property - def stripe(self): - """A material that fills the surface with alternating colors.""" - return self._stripe - - @property - def checkerboard(self): - """A material that fills the surface with a checkerboard pattern.""" - return self._checkerboard + solidColor = attr.ib(default=None) + image = attr.ib(default=None) + grid = attr.ib(default=None) + stripe = attr.ib(default=None) + checkerboard = attr.ib(default=None) +@attr.s(repr=False, frozen=True, kw_only=True) class PolylineMaterial(BaseCZMLObject): """"A definition of how a surface is colored or shaded.""" - KNOWN_PROPERTIES = [ - "solidColor", - "polylineOutline", - "polylineArrow", - "polylineDash", - "polylineGlow", - "image", - "grid", - "stripe", - "checkerboard", - ] - - def __init__( - self, *, solidColor=None, image=None, grid=None, stripe=None, checkerboard=None - ): - if isinstance(solidColor, Color): - solidColor = SolidColorMaterial(color=solidColor) - - self._solid_color = solidColor - self._image = image - self._grid = grid - self._stripe = stripe - self._checkerboard = checkerboard - - @property - def solidColor(self): - """A material that fills the surface with a solid color, which may be translucent.""" - return self._solid_color - - @property - def image(self): - """A material that fills the surface with an image.""" - return self._image - - @property - def grid(self): - """A material that fills the surface with a grid.""" - return self._grid - - @property - def stripe(self): - """A material that fills the surface with alternating colors.""" - return self._stripe - - @property - def checkerboard(self): - """A material that fills the surface with a checkerboard pattern.""" - return self._checkerboard + solidColor = attr.ib(default=None) + image = attr.ib(default=None) + grid = attr.ib(default=None) + stripe = attr.ib(default=None) + checkerboard = attr.ib(default=None) +@attr.s(repr=False, frozen=True, kw_only=True) class SolidColorMaterial(BaseCZMLObject): """A material that fills the surface with a solid color.""" - KNOWN_PROPERTIES = ["color"] - - def __init__(self, *, color=None): - self._color = color + color = attr.ib(default=None) - @property - def color(self): - """The color of the surface.""" - return self._color + @classmethod + def from_list(cls, color): + return cls(color=Color.from_list(color)) +@attr.s(repr=False, frozen=True, kw_only=True) class GridMaterial(BaseCZMLObject): """A material that fills the surface with a two-dimensional grid.""" - KNOWN_PROPERTIES = [ - "color", - "cellAlpha", - "lineCount", - "lineThickness", - "lineOffset", - ] - - def __init__( - self, - *, - color=None, - cellAlpha=0.1, - lineCount=[8, 8], - lineThickness=[1.0, 1.0], - lineOffset=[0.0, 0.0], - ): - self._color = color - self._cell_alpha = cellAlpha - self._line_count = lineCount - self._line_thickness = lineThickness - self._line_offset = lineOffset - - @property - def color(self): - """The color of the surface.""" - return self._color - - @property - def cellAlpha(self): - """The alpha value for the space between grid lines. - - This will be combined with the color alpha. - - """ - return self._cell_alpha - - @property - def lineCount(self): - """The number of grid lines along each axis.""" - return self._line_count - - @property - def lineThickness(self): - """The thickness of grid lines along each axis, in pixels.""" - return self._line_thickness - - @property - def lineOffset(self): - """The offset of grid lines along each axis, as a percentage from 0 to 1.""" - return self._line_offset + color = attr.ib(default=None) + cellAlpha = attr.ib(default=0.1) + lineCount = attr.ib(default=[8, 8]) + lineThickness = attr.ib(default=[1.0, 1.0]) + lineOffset = attr.ib(default=[0.0, 0.0]) +@attr.s(repr=False, frozen=True, kw_only=True) class StripeMaterial(BaseCZMLObject): """A material that fills the surface with alternating colors.""" - KNOWN_PROPERTIES = ["orientation", "evenColor", "oddColor", "offset", "repeat"] - - def __init__( - self, - *, - orientation="HORIZONTAL", - evenColor=None, - oddColor=None, - offset=0.0, - repeat=1.0, - ): - - self._orientation = orientation - self._even_color = evenColor - self._odd_color = oddColor - self._offset = offset - self._repeat = repeat - - @property - def orientation(self): - """The value indicating if the stripes are horizontal or vertical.""" - return self._orientation - - @property - def evenColor(self): - """The even color.""" - return self._even_color - - @property - def oddColor(self): - """The odd color.""" - return self._odd_color - - @property - def offset(self): - """The value indicating where in the pattern to begin drawing, with 0.0 being the beginning of the even color, - 1.0 the beginning of the odd color, 2.0 being the even color again, and any multiple or fractional values being - in between.""" - return self._offset - - @property - def repeat(self): - """The number of times the stripes repeat.""" - return self._repeat + orientation = attr.ib( + default="HORIZONTAL" + ) # TODO: https://github.com/AnalyticalGraphicsInc/czml-writer/wiki/StripeOrientationValue + evenColor = attr.ib(default=None) + oddColor = attr.ib(default=None) + offset = attr.ib(default=0.0) + repeat = attr.ib(default=1.0) +@attr.s(repr=False, frozen=True, kw_only=True) class CheckerboardMaterial(BaseCZMLObject): """A material that fills the surface with alternating colors.""" - KNOWN_PROPERTIES = ["orientation", "evenColor", "oddColor", "offset", "repeat"] - - def __init__(self, *, evenColor=None, oddColor=None, repeat=[1, 2]): - - self._even_color = evenColor - self._odd_color = oddColor - self._repeat = repeat - - @property - def evenColor(self): - """The even color.""" - return self._even_color - - @property - def oddColor(self): - """The odd color.""" - return self._odd_color - - @property - def repeat(self): - """The number of times the stripes repeat.""" - return self._repeat + evenColor = attr.ib(default=None) + oddColor = attr.ib(default=None) + repeat = attr.ib(default=None) +@attr.s(repr=False, frozen=True, kw_only=True) class ImageMaterial(BaseCZMLObject): """A material that fills the surface with an image.""" - KNOWN_PROPERTIES = ["image", "repeat", "color", "transparent"] - - def __init__(self, *, image=None, repeat=[1, 1], color=None, transparent=False): - - self._image = image - self._repeat = repeat - self._color = color - self._transparent = transparent - - @property - def image(self): - """The image to display on the surface.""" - return self._image - - @property - def repeat(self): - """The number of times the image repeats along each axis.""" - return self._repeat - - @property - def color(self): - """The color of the image. - - This color value is multiplied with the image to produce the final color. - - """ - return self._color - - @property - def transparent(self): - """Whether or not the image has transparency.""" - return self._transparent + image = attr.ib(default=None) + repeat = attr.ib(default=[1, 1]) + color = attr.ib(default=None) + transparent = attr.ib(default=False) +@attr.s(repr=False, frozen=True, kw_only=True) class Color(BaseCZMLObject, Interpolatable, Deletable): """A color. The color can optionally vary over time.""" - KNOWN_PROPERTIES = ["delete", "rgba", "rgbaf", "reference"] - - def __init__(self, *, delete=None, rgba=None, rgbaf=None): - - if isinstance(rgba, list): - rgba = RgbaValue(values=rgba) - if isinstance(rgbaf, list): - rgbaf = RgbafValue(values=rgbaf) - - self._delete = delete - self._rgba = rgba - self._rgbaf = rgbaf - - @property - def rgba(self): - """A color specified as an array of color components [Red, Green, Blue, Alpha] - where each component is in the range 0-255. - - If the array has four elements, the color is constant. - - If it has five or more elements, they are time-tagged samples arranged as - [Time, Red, Green, Blue, Alpha, Time, Red, Green, Blue, Alpha, ...], where Time - is an ISO 8601 date and time string or seconds since epoch. - """ - return self._rgba - - @property - def rgbaf(self): - """A color specified as an array of color components [Red, Green, Blue, Alpha] - where each component is in the range 0.0-1.0. - - If the array has four elements, the color is constant. - - If it has five or more elements, they are time-tagged - samples arranged as [Time, Red, Green, Blue, Alpha, Time, Red, Green, Blue, Alpha, ...], - where Time is an ISO 8601 date and time string or seconds since epoch. - - """ - return self._rgbaf + delete = attr.ib(default=None) + rgba = attr.ib(default=None) + rgbaf = attr.ib(default=None) + + @classmethod + def from_list(cls, color): + if all(isinstance(v, int) for v in color): + if len(color) == 3: + color = color + [255] + else: + color = color[:] + + return cls(rgba=RgbaValue(values=color)) + else: + if len(color) == 3: + color = color + [1.0] + else: + color = color[:] + + return cls(rgbaf=RgbafValue(values=color)) + + @classmethod + def from_hex(cls, color): + if color > 0xFFFFFF: + values = [ + (color & 0xFF000000) >> 24, + (color & 0x00FF0000) >> 16, + (color & 0x0000FF00) >> 8, + (color & 0x000000FF) >> 0, + ] + else: + values = [ + (color & 0xFF0000) >> 16, + (color & 0x00FF00) >> 8, + (color & 0x0000FF) >> 0, + 0xFF, + ] + + return cls.from_list(values) + + @classmethod + def from_str(cls, color): + return cls.from_hex(int(color.rsplit("#")[-1], 16)) # noinspection PyPep8Naming +@attr.s(repr=False, frozen=True, kw_only=True) class Position(BaseCZMLObject, Interpolatable, Deletable): """Defines a position. The position can optionally vary over time.""" - KNOWN_PROPERTIES = [ - "delete", - "epoch", - "interpolationAlgorithm", - "interpolationDegree", - "referenceFrame", - "cartesian", - "cartographicRadians", - "cartographicDegrees", - ] - - def __init__( - self, - *, - delete=None, - epoch=None, - interpolationAlgorithm=None, - interpolationDegree=None, - referenceFrame=None, - cartesian=None, - cartographicRadians=None, - cartographicDegrees=None, - ): + delete = attr.ib(default=None) + epoch = attr.ib(default=None) + interpolationAlgorithm = attr.ib(default=None) + interpolationDegree = attr.ib(default=None) + referenceFrame = attr.ib(default=None) + cartesian = attr.ib(default=None) + cartographicRadians = attr.ib(default=None) + cartographicDegrees = attr.ib(default=None) + + def __attrs_post_init__(self,): if all( - val is None for val in (cartesian, cartographicDegrees, cartographicRadians) + val is None + for val in ( + self.cartesian, + self.cartographicDegrees, + self.cartographicRadians, + ) ): raise ValueError( "One of cartesian, cartographicDegrees or cartographicRadians must be given" ) - if isinstance(cartesian, list): - cartesian = Cartesian3Value(values=cartesian) - if isinstance(cartographicRadians, list): - cartographicRadians = CartographicRadiansValue(values=cartographicRadians) - if isinstance(cartographicDegrees, list): - cartographicDegrees = CartographicDegreesValue(values=cartographicDegrees) - - self._delete = delete - self._epoch = epoch - self._interpolation_algorithm = interpolationAlgorithm - self._interpolation_degree = interpolationDegree - self._reference_frame = referenceFrame - self._cartesian = cartesian - self._cartographic_radians = cartographicRadians - self._cartographic_degrees = cartographicDegrees - - @property - def referenceFrame(self): - """The reference frame in which cartesian positions are specified.""" - return self._reference_frame - - @property - def cartesian(self): - """The position specified as a three-dimensional Cartesian value. - - The value [X, Y, Z] is specified - in meters relative to the ReferenceFrame. - """ - return self._cartesian - - @property - def cartographicRadians(self): - """The position specified in Cartographic WGS84 coordinates, [Longitude, Latitude, Height]. - - Longitude and Latitude are in radians and Height is in meters. - - """ - return self._cartographic_radians - - @property - def cartographicDegrees(self): - """The position specified in Cartographic WGS84 coordinates, [Longitude, Latitude, Height]. - - Longitude and Latitude are in degrees and Height is in meters. - - """ - return self._cartographic_degrees - # noinspection PyPep8Naming +@attr.s(repr=False, frozen=True, kw_only=True) class Billboard(BaseCZMLObject, HasAlignment): """A billboard, or viewport-aligned image. @@ -428,709 +169,163 @@ class Billboard(BaseCZMLObject, HasAlignment): A billboard is sometimes called a marker. """ - KNOWN_PROPERTIES = [ - "show", - "image", - "scale", - "pixelOffset", - "eyeOffset", - "horizontalOrigin", - "verticalOrigin", - "heightReference", - "color", - "rotation", - "alignedAxis", - "sizeInMeters", - "width", - "height", - "scaleByDistance", - "translucencyByDistance", - "pixelOffsetScaleByDistance", - "imageSubRegion", - "distanceDisplayCondition", - "disableDepthTestDistance", - ] - - def __init__( - self, - *, - image, - show=None, - scale=None, - horizontalOrigin=None, - verticalOrigin=None, - ): - if isinstance(image, str): - image = Uri(uri=image) - self._image = image - self._show = show - self._scale = scale - self._horizontal_origin = horizontalOrigin - self._vertical_origin = verticalOrigin - - @property - def show(self): - """Whether or not the billboard is shown.""" - return self._show - - @property - def image(self): - """The URI of the image displayed on the billboard. - - For broadest client compatibility, - the URI should be accessible via Cross-Origin Resource Sharing (CORS). - The URI may also be a data URI. - """ - return self._image - - @property - def scale(self): - """The scale of the billboard. - - The scale is multiplied with the pixel size of the billboard's image. - For example, if the scale is 2.0, - the billboard will be rendered with twice the number of pixels, - in each direction, of the image. - - """ - return self._scale + image = attr.ib() + show = attr.ib(default=None) + scale = attr.ib(default=None) + horizontalOrigin = attr.ib(default=None) + verticalOrigin = attr.ib(default=None) +@attr.s(repr=False, frozen=True, kw_only=True) class EllipsoidRadii(BaseCZMLObject, Deletable, Interpolatable): """The radii of an ellipsoid.""" - KNOWN_PROPERTIES = ["cartesian", "reference"] - - def __init__(self, *, cartesian=None, reference=None): - self._cartesian = cartesian - self._reference = reference - - @property - def cartesian(self): - """The radii specified as a three-dimensional Cartesian value [X, Y, Z], in world coordinates in meters.""" - return self._cartesian - - @property - def reference(self): - """The radii specified as a reference to another property.""" - return self._reference + cartesian = attr.ib(default=None) + reference = attr.ib(default=None) +@attr.s(repr=False, frozen=True, kw_only=True) class Polygon(BaseCZMLObject): """A polygon, which is a closed figure on the surface of the Earth.""" - KNOWN_PROPERTIES = [ - "show", - "positions", - "holes", - "arcType", - "height", - "heightReference", - "extrudedHeight", - "extrudedHeightReference", - "stRotation", - "granularity", - "fill", - "material", - "outline", - "outlineColor", - "outlineWidth", - "perPositionHeight", - "closeTop", - "closeBottom", - "shadows", - "distanceDisplayCondition", - "classificationType", - "zIndex", - ] - - def __init__( - self, - *, - positions, - show=None, - arcType=None, - granularity=None, - material=None, - shadows=None, - distanceDisplayCondition=None, - classificationType=None, - zIndex=None, - ): - self._position = positions - self._show = show - self._arc_type = arcType - self._granularity = granularity - self._material = material - self._shadows = shadows - self._distance_display_condition = distanceDisplayCondition - self._classification_type = classificationType - self._z_index = zIndex - - @property - def positions(self): - """The array of positions defining a simple polygon.""" - return self._position - - @property - def show(self): - """Whether or not the polygon is shown.""" - return self._show - - @property - def arcType(self): - """The type of arc that should connect the positions of the polygon.""" - return self._arc_type - - @property - def granularity(self): - """The sampling distance, in radians.""" - return self._granularity - - @property - def material(self): - """The material to use to fill the polygon.""" - return self._material - - @property - def shadows(self): - """Whether or not the polygon casts or receives shadows.""" - return self._shadows - - @property - def distanceDisplayCondition(self): - """The display condition specifying the distance from the camera at which this polygon will be displayed.""" - return self._distance_display_condition - - @property - def classificationType(self): - """Whether a classification affects terrain, 3D Tiles, or both.""" - return self._classification_type - - @property - def zIndex(self): - """The z-index of the polygon, used for ordering ground geometry. - - Only has an effect if the polygon is constant, - and height and extrudedHeight are not specified. - - """ - return self._z_index + positions = attr.ib() + show = attr.ib(default=None) + arcType = attr.ib(default=None) + granularity = attr.ib(default=None) + material = attr.ib(default=None) + shadows = attr.ib(default=None) + distanceDisplayCondition = attr.ib(default=None) + classificationType = attr.ib(default=None) + zIndex = attr.ib(default=None) +@attr.s(repr=False, frozen=True, kw_only=True) class Polyline(BaseCZMLObject): """A polyline, which is a line in the scene composed of multiple segments.""" - KNOWN_PROPERTIES = [ - "show", - "positions", - "arcType", - "width", - "granularity", - "material", - "followSurface", - "shadows", - "depthFailMaterial", - "distanceDisplayCondition", - "clampToGround", - "classificationType", - "zIndex", - ] - - def __init__( - self, - *, - positions, - show=None, - arcType=None, - width=None, - granularity=None, - material=None, - followSurface=None, - shadows=None, - depthFailMaterial=None, - distanceDisplayCondition=None, - clampToGround=None, - classificationType=None, - zIndex=None, - ): - self._position = positions - self._show = show - self._arc_type = arcType - self._width = width - self._granularity = granularity - self._material = material - self._follow_surface = followSurface - self._shadows = shadows - self._depth_fail_material = depthFailMaterial - self._distance_display_condition = distanceDisplayCondition - self._clamp_to_ground = clampToGround - self._classification_type = classificationType - self._z_index = zIndex - - @property - def positions(self): - """The array of positions defining the polyline as a line strip.""" - return self._position - - @property - def show(self): - """Whether or not the polyline is shown.""" - return self._show - - @property - def arcType(self): - """The type of arc that should connect the positions of the polyline.""" - return self._arc_type - - @property - def width(self): - """The width of the polyline.""" - return self._width - - @property - def granulariy(self): - """The sampling distance, in radians.""" - return self._granularity - - @property - def material(self): - """The material to use to draw the polyline.""" - return self._material - - @property - def followSurface(self): - """ Whether or not the positions are connected as great arcs (the default) or as straight lines. - This property has been superseded by arcType, which should be used instead. - """ - return self._follow_surface - - @property - def shadows(self): - """Whether or not the polyline casts or receives shadows.""" - return self._shadows - - @property - def depthFailMaterial(self): - """The material to use to draw the polyline when it is below the terrain.""" - return self._depth_fail_material - - @property - def distanceDisplayCondition(self): - """The display condition specifying at what distance from the camera this polyline will be displayed.""" - return self._distance_display_condition - - @property - def clampToGround(self): - """Whether or not the polyline should be clamped to the ground.""" - return self._clamp_to_ground - - @property - def classificationType(self): - """Whether a classification affects terrain, 3D Tiles, or both.""" - return self._classification_type - - @property - def zIndex(self): - """ The z-index of the polyline, used for ordering ground geometry. Only has an effect if the polyline is - constant, and clampToGround is true. - """ - return self._z_index - - + positions = attr.ib() + show = attr.ib(default=None) + arcType = attr.ib(default=None) + width = attr.ib(default=None) + granularity = attr.ib(default=None) + material = attr.ib(default=None) + followSurface = attr.ib(default=None) + shadows = attr.ib(default=None) + depthFailMaterial = attr.ib(default=None) + distanceDisplayCondition = attr.ib(default=None) + clampToGround = attr.ib(default=None) + classificationType = attr.ib(default=None) + zIndex = attr.ib(default=None) + + +@attr.s(repr=False, frozen=True, kw_only=True) class ArcType(BaseCZMLObject, Deletable): """The type of an arc.""" - KNOWN_PROPERTIES = ["arcType", "reference"] - - def __init__(self, *, arcType=None, reference=None): - - if isinstance(arcType, str): - arcType = ArcTypeValue(string=arcType) - - self._arc_type = arcType - self._reference = reference - - @property - def arcType(self): - """The arc type""" - return self._arc_type - - @property - def reference(self): - """The arc type specified as a reference to another property.""" - return self._reference + arcType = attr.ib(default=None) + reference = attr.ib(default=None) +@attr.s(repr=False, frozen=True, kw_only=True) class ShadowMode(BaseCZMLObject, Deletable): """Whether or not an object casts or receives shadows from each light source when shadows are enabled.""" - KNOWN_PROPERTIES = ["shadowMode", "reference"] - - def __init__(self, *, shadowMode=None, reference=None): - self._shadow_mode = shadowMode - self._reference = reference - - @property - def shadowMode(self): - """The shadow mode""" - return self._shadow_mode - - @property - def reference(self): - """The shadow mode specified as a reference to another property.""" - return self._reference + shadowMode = attr.ib(default=None) + referenec = attr.ib(default=None) +@attr.s(repr=False, frozen=True, kw_only=True) class ClassificationType(BaseCZMLObject, Deletable): """Whether a classification affects terrain, 3D Tiles, or both.""" - KNOWN_PROPERTIES = ["classificationType", "reference"] - - def __init__(self, *, classificationType=None, reference=None): - self._classification_type = classificationType - self._reference = reference - - @property - def classificationType(self): - """The classification type, which indicates whether a classification affects terrain, 3D Tiles, or both.""" - return self._classification_type - - @property - def reference(self): - """The classification type specified as a reference to another property.""" - return self._reference + classificationType = attr.ib(default=None) + reference = attr.ib(default=None) +@attr.s(repr=False, frozen=True, kw_only=True) class DistanceDisplayCondition(BaseCZMLObject, Interpolatable, Deletable): """Indicates the visibility of an object based on the distance to the camera.""" - KNOWN_PROPERTIES = ["distanceDisplayCondition", "reference"] - - def __init__(self, *, distanceDisplayCondition=None, reference=None): - self._distance_display_condition = distanceDisplayCondition - self._reference = reference - - @property - def distanceDisplayCondition(self): - """The value specified as two values [NearDistance, FarDistance], with distances in meters.""" - return self._distance_display_condition - - @property - def reference(self): - """The value specified as a reference to another property.""" - return self._reference + distanceDisplayCondition = attr.ib(default=None) + reference = attr.ib(default=None) +@attr.s(repr=False, frozen=True, kw_only=True) class PositionList(BaseCZMLObject, Deletable): """A list of positions.""" - KNOWN_PROPERTIES = [ - "referenceFrame", - "cartersian", - "cartographicRadians", - "cartographicDegrees", - "references", - ] - - def __init__( - self, - *, - referenceFrame=None, - cartesian=None, - cartographicRadians=None, - cartographicDegrees=None, - references=None, - ): - self._reference_frames = referenceFrame - self._cartesian = cartesian - self._cartographic_radians = cartographicRadians - self._cartographic_degrees = cartographicDegrees - self._references = references - - @property - def referenceFrame(self): - """The reference frame in which cartesian positions are specified. Possible values are "FIXED" and - "INERTIAL".""" - return self._reference_frames - - @property - def cartesian(self): - """The list of positions specified as three-dimensional Cartesian values, [X, Y, Z, X, Y, Z, ...], - in meters relative to the referenceFrame.""" - return self._cartesian - - @property - def cartographicRadians(self): - """The list of positions specified in Cartographic WGS84 coordinates, [Longitude, Latitude, Height, Longitude, - Latitude, Height, ...], where Longitude and Latitude are in radians and Height is in meters.""" - return self._cartographic_radians - - @property - def cartographicDegrees(self): - """The list of positions specified in Cartographic WGS84 coordinates, [Longitude, Latitude, Height, Longitude, - Latitude, Height, ...], where Longitude and Latitude are in degrees and Height is in meters.""" - return self._cartographic_degrees - - @property - def references(self): - """The list of positions specified as references. Each reference is to a property that defines a single - position, which may change with time.""" - return self._references + referenceFrame = attr.ib(default=None) + cartesian = attr.ib(default=None) + cartographicRadians = attr.ib(default=None) + cartographicDegrees = attr.ib(default=None) + references = attr.ib(default=None) +@attr.s(repr=False, frozen=True, kw_only=True) class Ellipsoid(BaseCZMLObject): """A closed quadric surface that is a three-dimensional analogue of an ellipse.""" - KNOWN_PROPERTIES = [ - "radii", - "show", - "heightReference", - "fill", - "material", - "outline", - "outlineColor", - "outlineWidth", - "stackPartitions", - "slicePartitions", - "subdivisions", - "shadows", - "distanceDisplayCondition", - ] - - def __init__( - self, - *, - radii, - show=None, - heightReference=None, - fill=None, - material=None, - outline=None, - outlineColor=None, - outlineWidth=None, - stackPartitions=None, - slicePartitions=None, - subdivisions=None, - ): - self._radii = radii - self._show = show - self._height_reference = heightReference - self._fill = fill - self._material = material - self._outline = outline - self._outline_color = outlineColor - self._outline_width = outlineWidth - self._stack_partitions = stackPartitions - self._slice_partitions = slicePartitions - self._subdivisions = subdivisions - - @property - def show(self): - """Whether or not the ellipsoid is shown.""" - return self._show - - @property - def radii(self): - """The dimensions of the ellipsoid.""" - return self._radii - - @property - def heightReference(self): - """The height reference of the ellipsoid, which indicates if the position is relative to terrain or not.""" - return self._height_reference - - @property - def fill(self): - """Whether or not the ellipsoid is filled.""" - return self._fill - - @property - def material(self): - """The material to display on the surface of the ellipsoid.""" - return self._material - - @property - def outline(self): - """Whether or not the ellipsoid is outlined.""" - return self._outline - - @property - def outlineColor(self): - """The color of the ellipsoid outline.""" - return self._outline_color - - @property - def outlineWidth(self): - """The width of the ellipsoid outline.""" - return self._outline_width - - @property - def stackPartitions(self): - """The number of times to partition the ellipsoid into stacks.""" - return self._stack_partitions - - @property - def slicePartitions(self): - """The number of times to partition the ellipsoid into radial slices.""" - return self._slice_partitions - - @property - def subdivisions(self): - """The number of samples per outline ring, determining the granularity of the curvature.""" - return self._subdivisions + radii = attr.ib() + show = attr.ib(default=None) + heightReference = attr.ib(default=None) + fill = attr.ib(default=None) + material = attr.ib(default=None) + outline = attr.ib(default=None) + outlineColor = attr.ib(default=None) + outlineWidth = attr.ib(default=None) + stackPartitions = attr.ib(default=None) + slicePartitions = attr.ib(default=None) + subdivisions = attr.ib(default=None) +@attr.s(repr=False, frozen=True, kw_only=True) class Box(BaseCZMLObject): """A box, which is a closed rectangular cuboid.""" - KNOWN_PROPERTIES = [ - "show", - "dimensions", - "heightReference", - "fill", - "material", - "outline", - "outlineColor", - "outlineWidth", - "shadows", - "distanceDisplayCondition", - ] - - def __init__( - self, - show=None, - dimensions=None, - heightReference=None, - fill=None, - material=None, - outline=None, - outlineColor=None, - outlineWidth=None, - shadows=None, - distanceDisplayCondition=None, - ): - self._show = show - self._dimensions = dimensions - self._heightReference = heightReference - self._fill = fill - self._material = material - self._outline = outline - self._outlineColor = outlineColor - self._outlineWidth = outlineWidth - self._shadows = shadows - self._distanceDisplayCondition = distanceDisplayCondition - - @property - def show(self): - """Whether or not the box is shown.""" - return self._show - - @property - def dimensions(self): - """The dimensions of the box.""" - return self._dimensions - - @property - def heightReference(self): - """The height reference of the box, which indicates if the position is relative to terrain or not.""" - return self._heightReference - - @property - def fill(self): - """Whether or not the box is filled.""" - return self._fill - - @property - def material(self): - """The material to display on the surface of the box.""" - return self._material - - @property - def outline(self): - """Whether or not the box is outlined.""" - return self._outline - - @property - def outlineColor(self): - """The color of the box outline.""" - return self._outlineColor - - @property - def outlineWidth(self): - """The width of the box outline.""" - return self._outlineWidth - - @property - def shadows(self): - """Whether or not the box casts or receives shadows.""" - return self._shadows - - @property - def distanceDisplayCondition(self): - """The display condition specifying the distance from the camera at which this box will be displayed.""" - return self._distanceDisplayCondition + show = attr.ib(default=None) + dimensions = attr.ib(default=None) + heightReference = attr.ib(default=None) + fill = attr.ib(default=None) + material = attr.ib(default=None) + outline = attr.ib(default=None) + outlineColor = attr.ib(default=None) + outlineWidth = attr.ib(default=None) + shadows = attr.ib(default=None) + distanceDisplayCondition = attr.ib(default=None) +@attr.s(repr=False, frozen=True, kw_only=True) class BoxDimensions(BaseCZMLObject, Interpolatable): """The width, depth, and height of a box.""" - KNOWN_PROPERTIES = ["cartesian", "reference"] - - def __init__(self, *, cartesian=None, reference=None): - self._cartesian = cartesian - self._reference = reference - - @property - def cartesian(self): - """The dimensions specified as a three-dimensional Cartesian value [X, Y, Z], with X representing width, Y representing depth, and Z representing height, in world coordinates in meters.""" - return self._cartesian - - @property - def reference(self): - """The dimensions specified as a reference to another property.""" - return self._reference + cartesian = attr.ib(default=None) + reference = attr.ib(default=None) +@attr.s(repr=False, frozen=True, kw_only=True) class EyeOffset(BaseCZMLObject, Deletable): - """An offset in eye coordinates which can optionally vary over time. Eye coordinates are a left-handed coordinate system where the X-axis points toward the viewer's right, the Y-axis poitns up, and the Z-axis points into the screen.""" - - KNOWN_PROPERTIES = ["cartesian", "reference"] + """An offset in eye coordinates which can optionally vary over time. - def __init__(self, *, cartesian=None, reference=None): - self._cartesian = cartesian - self._reference = reference + Eye coordinates are a left-handed coordinate system + where the X-axis points toward the viewer's right, + the Y-axis poitns up, and the Z-axis points into the screen. - @property - def cartesian(self): - """The eye offset specified as a three-dimensional Cartesian value [X, Y, Z], in eye coordinates in meters. If the array has three elements, the eye offset is constant. If it has four or more elements, they are time-tagged samples arranged as [Time, X, Y, Z, Time, X, Y, Z, ...], where Time is an ISO 8601 date and time string or seconds since epoch.""" - return self._cartesian + """ - @property - def reference(self): - """The eye offset specified as a reference to another property.""" - return self._reference + cartesian = attr.ib(default=None) + reference = attr.ib(default=None) +@attr.s(repr=False, frozen=True, kw_only=True) class HeightReference(BaseCZMLObject, Deletable): """The height reference of an object, which indicates if the object's position is relative to terrain or not.""" - KNOWN_PROPERTIES = ["heightReference", "reference"] - - def __init__(self, *, heightReference=None, reference=None): - self._height_reference = heightReference - self._reference = reference - - @property - def heightReference(self): - return self._height_reference - - @property - def reference(self): - return self._reference + heightReference = attr.ib(default=None) + reference = attr.ib(default=None) # noinspection PyPep8Naming +@attr.s(repr=False, frozen=True, kw_only=True) class Clock(BaseCZMLObject): """Initial settings for a simulated clock when a document is loaded. @@ -1138,51 +333,14 @@ class Clock(BaseCZMLObject): """ - KNOWN_PROPERTIES = ["currentTime", "multiplier", "range", "step"] - - def __init__( - self, - *, - currentTime=None, - multiplier=1.0, - range=ClockRanges.LOOP_STOP, - step=ClockSteps.SYSTEM_CLOCK_MULTIPLIER, - ): - self._current_time = currentTime - self._multiplier = multiplier - self._range = range - self._step = step - - @property - def currentTime(self): - """The current time, specified in ISO8601 format.""" - return self._current_time - - @property - def multiplier(self): - """The multiplier. - - When step is set to TICK_DEPENDENT, - this is the number of seconds to advance each tick. - When step is set to SYSTEM_CLOCK_DEPENDENT, - this is multiplied by the elapsed system time between ticks. - This value is ignored in SYSTEM_CLOCK mode. - - """ - return self._multiplier - - @property - def range(self): - """The behavior when the current time reaches its start or end times.""" - return self._range - - @property - def step(self): - """How the current time advances each tick.""" - return self._step + currentTime = attr.ib(default=None) + multiplier = attr.ib(default=1.0) + range = attr.ib(default=ClockRanges.LOOP_STOP) + step = attr.ib(default=ClockSteps.SYSTEM_CLOCK_MULTIPLIER) # noinspection PyPep8Naming +@attr.s(repr=False, frozen=True, kw_only=True) class Path(BaseCZMLObject): """A path, which is a polyline defined by the motion of an object over time. @@ -1194,193 +352,32 @@ class Path(BaseCZMLObject): """ - KNOWN_PROPERTIES = [ - "show", - "leadTime", - "trailTime", - "width", - "resolution", - "material", - "distanceDisplayCondition", - ] - - def __init__( - self, - *, - show=True, - leadTime=None, - trailTime=None, - width=1.0, - resolution=60.0, - material=None, - distanceDisplayCondition=None, - ): - self._show = show - self._lead_time = leadTime - self._trail_time = trailTime - self._width = width - self._resolution = resolution - self._material = material - self._distance_display_condition = distanceDisplayCondition - - @property - def show(self): - """Whether or not the path is shown.""" - return self._show - - @property - def leadTime(self): - """The time ahead of the animation time, in seconds, to show the path. - - The time will be limited to not exceed the object's availability. - By default, the value is unlimited, - which effectively results in drawing the entire available path of the object. - - """ - return self._lead_time - - @property - def trailTime(self): - """The time behind the animation time, in seconds, to show the path. - - The time will be limited to not exceed the object's availability. - By default, the value is unlimited, - which effectively results in drawing the entire available path of the object. - - """ - return self._trail_time - - @property - def width(self): - """The width of the path line.""" - return self._width - - @property - def resolution(self): - """The maximum step-size, in seconds, used to sample the path. - - If the position property has data points - farther apart than resolution specifies, - additional samples will be computed, - creating a smoother path. - - """ - return self._resolution - - @property - def distanceDisplayCondition(self): - """The display condition specifying at what - distance from the camera this path will be displayed. - - """ - return self._distance_display_condition - - @property - def material(self): - """The material to use to draw the path.""" - return self._material + show = attr.ib(default=None) + leadTime = attr.ib(default=None) + trailTime = attr.ib(default=None) + width = attr.ib(default=1.0) + resolution = attr.ib(default=60.0) + material = attr.ib(default=None) + distanceDisplayCondition = attr.ib(default=None) +@attr.s(repr=False, frozen=True, kw_only=True) class Point(BaseCZMLObject): """A point, or viewport-aligned circle.""" - KNOWN_PROPERTIES = [ - "show", - "pixelSize", - "heightReference", - "color", - "outlineColor", - "outlineWidth", - "scaleByDistance", - "translucencyByDistance", - "distanceDisplayCondition", - "disableDepthTestDistance", - ] - - def __init__( - self, - *, - show=None, - pixelSize=None, - heightReference=None, - color=None, - outlineColor=None, - outlineWidth=None, - scaleByDistance=None, - translucencyByDistance=None, - distanceDisplayCondition=None, - disableDepthTestDistance=None, - ): - self._show = show - self._pixel_size = pixelSize - self._height_reference = heightReference - self._color = color - self._outline_color = outlineColor - self._outline_width = outlineWidth - self._scale_by_distance = scaleByDistance - self._translucency_by_distance = translucencyByDistance - self._distance_display_condition = distanceDisplayCondition - self._disable_depth_test_distance = disableDepthTestDistance - - @property - def show(self): - """Whether or not the point is shown.""" - return self._show - - @property - def pixelSize(self): - """The size of the point, in pixels.""" - return self._pixel_size - - @property - def heightReference(self): - """The height reference of the point, which indicates if the position is relative to terrain or not.""" - return self._height_reference - - @property - def color(self): - """The color of the point.""" - return self._color - - @property - def outlineColor(self): - """The color of the outline of the point.""" - return self._outline_color - - @property - def outlineWidth(self): - """The width of the outline of the point.""" - return self._outline_width - - @property - def scaleByDistance(self): - """ How the point's scale should change based on the point's distance from the camera. - This scalar value will be multiplied by pixelSize. - """ - return self._scale_by_distance - - @property - def translucencyByDistance(self): - """ How the point's translucency should change based on the point's distance from the camera. - This scalar value should range from 0 to 1. - """ - return self._translucency_by_distance - - @property - def distanceDisplayCondition(self): - """The display condition specifying the distance from the camera at which this point will be displayed.""" - return self._distance_display_condition - - @property - def disableDepthTestDistance(self): - """ The distance from the camera at which to disable the depth test. This can be used to prevent clipping - against terrain, for example. When set to zero, the depth test is always applied. - - When set to Infinity, the depth test is never applied. - """ - return self._disable_depth_test_distance + show = attr.ib(default=None) + pixelSize = attr.ib(default=None) + heightReference = attr.ib(default=None) + color = attr.ib(default=None) + outlineColor = attr.ib(default=None) + outlineWidth = attr.ib(default=None) + scaleByDistance = attr.ib(default=None) + translucencyByDistance = attr.ib(default=None) + distanceDisplayCondition = attr.ib(default=None) + disableDepthTestDistance = attr.ib(default=None) +@attr.s(repr=False, frozen=True, kw_only=True) class NearFarScalar(BaseCZMLObject, Interpolatable, Deletable): """ A numeric value which will be linearly interpolated between two values based on an object's distance from the camera, in eye coordinates. @@ -1390,148 +387,30 @@ class NearFarScalar(BaseCZMLObject, Interpolatable, Deletable): less than the near distance or greater than the far distance, respectively. """ - KNOWN_PROPERTIES = ["nearFarScalar", "reference"] - - def __init__(self, *, nearFarScalar=None, reference=None): - self._near_far_scalar = nearFarScalar - self._reference = reference - - @property - def nearFarScalar(self): - """ The value specified as four values [NearDistance, NearValue, FarDistance, FarValue], with distances in eye - coordinates in meters. - """ - return self._near_far_scalar - - @property - def reference(self): - """The value specified as a reference to another property.""" - return self._reference + nearFarScalar = attr.ib(default=None) + reference = attr.ib(default=None) # noinspection PyPep8Naming +@attr.s(repr=False, frozen=True, kw_only=True) class Label(BaseCZMLObject, HasAlignment): """A string of text.""" - KNOWN_PROPERTIES = [ - "show", - "text", - "font", - "style", - "scale", - "showBackground", - "backgroundColor", - "backgroundPadding", - "pixelOffset", - "eyeOffset", - "horizontalOrigin", - "verticalOrigin", - "heightReference", - "fillColor", - "outlineColor", - "outlineWidth", - "translucencyByDistance", - "pixelOffsetScaleByDistance", - "scaleByDistance", - "distanceDisplayCondition", - "disableDepthTestDistance", - ] - - def __init__( - self, - *, - show=True, - text=None, - font=None, - style=LabelStyles.FILL, - scale=None, - showBackground=None, - backgroundColor=None, - horizontalOrigin=None, - verticalOrigin=None, - fillColor=None, - outlineColor=None, - outlineWidth=1.0, - ): - - if isinstance(font, str): - font = FontValue(font=font) - - self._show = show - self._text = text - self._font = font - self._style = style - self._scale = scale - self._show_background = showBackground - self._background_color = backgroundColor - self._horizontal_origin = horizontalOrigin - self._vertical_origin = verticalOrigin - self._fill_color = fillColor - self._outline_color = outlineColor - self._outline_width = outlineWidth - - @property - def show(self): - """Whether or not the label is shown.""" - return self._show - - @property - def text(self): - """The text displayed by the label. - - The newline character (\n) indicates line breaks. - - """ - return self._text - - @property - def font(self): - """The font to use for the label.""" - return self._font - - @property - def style(self): - """The style of the label.""" - return self._style - - @property - def scale(self): - """The scale of the label. - - The scale is multiplied with the pixel size of the label's text. - For example, if the scale is 2.0, - the label will be rendered with twice the number of pixels, - in each direction, of the text. - - """ - return self._scale - - @property - def showBackground(self): - """Whether or not a background behind the label is shown.""" - return self._show_background - - @property - def backgroundColor(self): - """The color of the background behind the label.""" - return self._background_color - - @property - def fillColor(self): - """The fill color of the label.""" - return self._fill_color - - @property - def outlineColor(self): - """The outline color of the label.""" - return self._outline_color - - @property - def outlineWidth(self): - """The outline width of the label.""" - return self._outline_width - - + show = attr.ib(default=True) + text = attr.ib(default=None) + font = attr.ib(default=None) + style = attr.ib(default=LabelStyles.FILL) + scale = attr.ib(default=None) + showBackground = attr.ib(default=None) + backgroundColor = attr.ib(default=None) + horizontalOrigin = attr.ib(default=None) + verticalOrigin = attr.ib(default=None) + fillColor = attr.ib(default=None) + outlineColor = attr.ib(default=None) + outlineWidth = attr.ib(default=1.0) + + +@attr.s(repr=False, frozen=True, kw_only=True) class Orientation(BaseCZMLObject): """Defines an orientation. @@ -1540,206 +419,50 @@ class Orientation(BaseCZMLObject): """ - KNOWN_PROPERTIES = [ - "unitQuaternion", - "reference", - "velocityReference", - ] - - def __init__( - self, *, unitQuaternion=None, reference=None, velocityReference=None, - ): - if not isinstance(unitQuaternion, UnitQuaternionValue): - unitQuaternion = UnitQuaternionValue(values=unitQuaternion) - - self._unit_quaternion = unitQuaternion - self._reference = reference - self._velocity_reference = velocityReference - - @property - def unitQuaternion(self): - """The orientation specified as a 4-dimensional unit magnitude quaternion, specified as [X, Y, Z, W].""" - return self._unit_quaternion - - @property - def reference(self): - """The orientation specified as a reference to another property.""" - return self._reference - - @property - def velocityReference(self): - """The orientation specified as the normalized velocity vector of a position property. - - The reference must be to a position property. - - """ - return self._velocity_reference + unitQuaternion = attr.ib(default=None) + reference = attr.ib(default=None) + velocityReference = attr.ib(default=None) +@attr.s(repr=False, frozen=True, kw_only=True) class Model(BaseCZMLObject): """A 3D model.""" - KNOWN_PROPERTIES = [ - "show", - "gltf", - "scale", - "minimumPixelSize", - "maximumScale", - "incrementallyLoadTextures", - "runAnimations", - "shadows", - "heightReference", - "silhouetteColor", - "silhouetteSize", - "color", - "colorBlendMode", - "colorBlendAmount", - "distanceDisplayCondition", - "nodeTransformations", - "articulations", - ] - - def __init__( - self, - *, - show=None, - gltf, - scale=None, - minimumPixelSize=None, - maximumScale=None, - incrementallyLoadTextures=None, - runAnimations=None, - shadows=None, - heightReference=None, - silhouetteColor=None, - silhouetteSize=None, - color=None, - colorBlendMode=None, - colorBlendAmount=None, - distanceDisplayCondition=None, - nodeTransformations=None, - articulations=None, - ): - if not isinstance(gltf, Uri): - gltf = Uri(uri=gltf) - - self._show = show - self._gltf = gltf - self._scale = scale - self._minimum_pixel_size = minimumPixelSize - self._maximum_scale = maximumScale - self._incrementally_load_textures = incrementallyLoadTextures - self._run_animations = runAnimations - self._shadows = shadows - self._height_reference = heightReference - self._silhouette_color = silhouetteColor - self._silhouette_size = silhouetteSize - self._color = color - self._color_blend_mode = colorBlendMode - self._color_blend_amount = colorBlendAmount - self._distance_display_condition = distanceDisplayCondition - self._node_transformations = nodeTransformations - self._articulations = articulations - - @property - def show(self): - """Whether or not the model is shown.""" - return self._show - - @property - def gltf(self): - """The URI of a glTF model. - - For broadest client compatibility, the URI should be accessible via Cross-Origin Resource Sharing (CORS). - The URI may also be a data URI. - - """ - return self._gltf - - @property - def scale(self): - """The scale of the model.""" - return self._scale - - @property - def minimumPixelSize(self): - """The approximate minimum pixel size of the model regardless of zoom.""" - return self._minimum_pixel_size - - @property - def maximumScale(self): - """The maximum scale size of the model. - - This is used as an upper limit for minimumPixelSize. - - """ - return self._maximum_scale - - @property - def incrementallyLoadTextures(self): - """Whether or not the model can be rendered before all textures have loaded.""" - return self._incrementally_load_textures - - @property - def runAnimations(self): - """Whether or not to run all animations defined in the glTF model.""" - return self._run_animations - - @property - def shadows(self): - """Whether or not the model casts or receives shadows.""" - return self._shadows - - @property - def heightReference(self): - """The height reference of the model, which indicates if the position is relative to terrain or not.""" - return self._height_reference - - @property - def silhouetteColor(self): - """The color of the silhouette drawn around the model.""" - return self._silhouette_color - - @property - def silhouetteSize(self): - """The size, in pixels, of the silhouette drawn around the model.""" - return self._silhouette_size - - @property - def color(self): - """The color to blend with the model's rendered color.""" - return self._color - - @property - def colorBlendMode(self): - """The mode to use for blending between color and the model's color.""" - return self._color_blend_mode - - @property - def colorBlendAmount(self): - """The color strength when colorBlendMode is MIX. - - A value of 0.0 results in the model's rendered color while a value of 1.0 results in a solid color, - with any value in-between resulting in a mix of the two. - - """ - return self._color_blend_amount - - @property - def distanceDisplayCondition(self): - """The display condition specifying at what distance from the camera this model will be displayed.""" - return self._distance_display_condition - - @property - def nodeTransformations(self): - """A mapping of node names to node transformations.""" - return self._node_transformations - - @property - def articulations(self): - """A mapping of keys to articulation values. - - The keys are the name of the articulation, a single space, and the name of the stage. + show = attr.ib(default=None) + gltf = attr.ib() + scale = attr.ib(default=None) + minimumPixelSize = attr.ib(default=None) + maximumScale = attr.ib(default=None) + incrementallyLoadTextures = attr.ib(default=None) + runAnimations = attr.ib(default=None) + shadows = attr.ib(default=None) + heightReference = attr.ib(default=None) + silhouetteColor = attr.ib(default=None) + silhouetteSize = attr.ib(default=None) + color = attr.ib(default=None) + colorBlendMode = attr.ib(default=None) + colorBlendAmount = attr.ib(default=None) + distanceDisplayCondition = attr.ib(default=None) + nodeTransformations = attr.ib(default=None) + articulations = attr.ib(default=None) + + +@attr.s(repr=False, frozen=True, kw_only=True) +class Uri(BaseCZMLObject, Deletable): + """A URI value. + + The URI can optionally vary with time. + """ + + delete = attr.ib(default=None) + uri = attr.ib(default=None) + + def __attrs_post_init__(self): + try: + parse_data_uri(self.uri) + except ValueError as e: + if not is_url(self.uri): + raise ValueError("uri must be a URL or a data URI") from e - """ - return self._articulations + def to_json(self): + return self.uri diff --git a/src/czml3/types.py b/src/czml3/types.py index 285627c..1d91689 100644 --- a/src/czml3/types.py +++ b/src/czml3/types.py @@ -1,10 +1,9 @@ import datetime as dt +import attr from dateutil.parser import isoparse as parse_iso_date -from w3lib.url import is_url, parse_data_uri from .base import BaseCZMLObject -from .common import Deletable from .constants import ISO8601_FORMAT_Z TYPE_MAPPING = {bool: "boolean"} @@ -31,44 +30,38 @@ def format_datetime_like(dt_object): return result +@attr.s(repr=False, frozen=True, kw_only=True) class _TimeTaggedCoords(BaseCZMLObject): NUM_COORDS: int - def __init__(self, *, values): + values = attr.ib() + + def __attrs_post_init__(self): if not ( - len(values) == self.NUM_COORDS or len(values) % (self.NUM_COORDS + 1) == 0 + len(self.values) == self.NUM_COORDS + or len(self.values) % (self.NUM_COORDS + 1) == 0 ): raise ValueError( "Input values must have either 3 or N * 4 values, " "where N is the number of time-tagged samples." ) - self._values = values - - @property - def values(self): - return self._values - def to_json(self): - return list(self._values) + return list(self.values) +@attr.s(repr=False, frozen=True, kw_only=True) class FontValue(BaseCZMLObject): """A font, specified using the same syntax as the CSS "font" property.""" - def __init__(self, *, font=None): - self._font = font - - @property - def font(self): - """The font to use for the label.""" - return self._font + font = attr.ib(default=None) def to_json(self): - return self._font + return self.font +@attr.s(repr=False, frozen=True, kw_only=True) class RgbafValue(BaseCZMLObject): """A color specified as an array of color components [Red, Green, Blue, Alpha] where each component is in the range 0.0-1.0. If the array has four elements, @@ -78,102 +71,90 @@ class RgbafValue(BaseCZMLObject): """ - def __init__(self, *, values): - if not (len(values) == 4 or len(values) % 5 == 0): + values = attr.ib() + + def __attrs_post_init__(self): + if not (len(self.values) == 4 or len(self.values) % 5 == 0): raise ValueError( "Input values must have either 4 or N * 5 values, " "where N is the number of time-tagged samples." ) - if len(values) == 4: - if not all([0 <= val <= 1 for val in values]): + if len(self.values) == 4: + if not all([0 <= val <= 1 for val in self.values]): raise ValueError("Color values must be floats in the range 0-1.") else: - for i in range(0, len(values), 5): - v = values[i + 1 : i + 5] + for i in range(0, len(self.values), 5): + v = self.values[i + 1 : i + 5] if not all([0 <= val <= 1 for val in v]): raise ValueError("Color values must be floats in the range 0-1.") - self._values = values - - @property - def values(self): - return self._values - def to_json(self): - return list(self._values) + return list(self.values) +@attr.s(repr=False, frozen=True, kw_only=True) class RgbaValue(BaseCZMLObject): """A color specified as an array of color components [Red, Green, Blue, Alpha] - where each component is in the range 0-255. If the array has four elements, - the color is constant. + where each component is in the range 0-255. If the array has four elements, + the color is constant. - If it has five or more elements, they are time-tagged samples arranged as - [Time, Red, Green, Blue, Alpha, Time, Red, Green, Blue, Alpha, ...], where Time - is an ISO 8601 date and time string or seconds since epoch. + If it has five or more elements, they are time-tagged samples arranged as + [Time, Red, Green, Blue, Alpha, Time, Red, Green, Blue, Alpha, ...], where Time + is an ISO 8601 date and time string or seconds since epoch. + + """ - """ + values = attr.ib() - def __init__(self, *, values): - if not (len(values) == 4 or len(values) % 5 == 0): + def __attrs_post_init__(self): + if not (len(self.values) == 4 or len(self.values) % 5 == 0): raise ValueError( "Input values must have either 4 or N * 5 values, " "where N is the number of time-tagged samples." ) - if len(values) == 4: - if not all([type(val) is int and 0 <= val <= 255 for val in values]): + if len(self.values) == 4: + if not all([type(val) is int and 0 <= val <= 255 for val in self.values]): raise ValueError("Color values must be integers in the range 0-255.") else: - for i in range(0, len(values), 5): - v = values[i + 1 : i + 5] + for i in range(0, len(self.values), 5): + v = self.values[i + 1 : i + 5] if not all([type(val) is int and 0 <= val <= 255 for val in v]): raise ValueError( "Color values must be integers in the range 0-255." ) - self._values = values - - @property - def values(self): - return self._values - def to_json(self): - return list(self._values) + return list(self.values) +@attr.s(repr=False, frozen=True, kw_only=True) class ReferenceValue(BaseCZMLObject): """ Represents a reference to another property. References can be used to specify that two properties on different objects are in fact, the same property. """ - def __init__(self, *, string=None): + string = attr.ib(default=None) - if not isinstance(string, str): + def __attrs_post_init__(self): + if not isinstance(self.string, str): raise ValueError("Reference must be a string") - if "#" not in string: + if "#" not in self.string: raise ValueError( "Invalid reference string format. Input must be of the form id#property" ) - self._string = string - - @property - def string(self): - """ Represents a reference to another property. References can be used to specify that two properties on - different objects are in fact, the same property. - """ - return self._string def to_json(self): - return self._string + return self.string +@attr.s(repr=False, frozen=True, kw_only=True) class Cartesian3Value(_TimeTaggedCoords): """A three-dimensional Cartesian value specified as [X, Y, Z]. @@ -187,6 +168,7 @@ class Cartesian3Value(_TimeTaggedCoords): NUM_COORDS = 3 +@attr.s(repr=False, frozen=True, kw_only=True) class CartographicRadiansValue(_TimeTaggedCoords): """A geodetic, WGS84 position specified as [Longitude, Latitude, Height]. @@ -201,6 +183,7 @@ class CartographicRadiansValue(_TimeTaggedCoords): NUM_COORDS = 3 +@attr.s(repr=False, frozen=True, kw_only=True) class CartographicDegreesValue(_TimeTaggedCoords): """A geodetic, WGS84 position specified as [Longitude, Latitude, Height]. @@ -215,125 +198,54 @@ class CartographicDegreesValue(_TimeTaggedCoords): NUM_COORDS = 3 -class StringValue(BaseCZMLObject, Deletable): +@attr.s(repr=False, frozen=True, kw_only=True) +class StringValue(BaseCZMLObject): """A string value. The string can optionally vary with time. """ - def __init__(self, *, delete=None, string=None): - self._delete = delete - self._string = string - - @property - def string(self): - """The string value.""" - return self._string + string = attr.ib(default=None) def to_json(self): - return self._string - - -class ArcTypeValue(BaseCZMLObject): - """The type of an arc""" - - def __init__(self, *, string=None): - valid_values = ["NONE", "GEODESIC", "RHUMB"] - - if string not in valid_values: - raise ValueError("Invalid input value") - - self._string = string - - @property - def string(self): - """The string value""" - return self._string - - def to_json(self): - return self._string - - -class ShadowModeValue(BaseCZMLObject, Deletable): - """Whether or not an object casts or receives shadows from each light source when shadows are enabled.""" - - def __init__(self, *, string=None): - valid_values = ["DISABLED", "ENABLED", "CAST_ONLY", "RECEIVE_ONLY"] - - if string not in valid_values: - raise ValueError("Invalid input value") - - self._string = string - - @property - def string(self): - """The string value""" - return self._string - - def to_json(self): - return self._string - - -class ClassificationTypeValue(BaseCZMLObject): - """Whether a classification affects terrain, 3D Tiles, or both.""" - - def __init__(self, *, string=None): - valid_values = ["TERRAIN", "CESIUM_3D_TILE", "BOTH"] - - if string not in valid_values: - raise ValueError("Invalid input value") - - self._string = string - - @property - def string(self): - """The string value""" - return self._string - - def to_json(self): - return self._string + return self.string +@attr.s(repr=False, frozen=True, kw_only=True) class CartographicRadiansListValue(BaseCZMLObject): """A list of geodetic, WGS84 positions specified as [Longitude, Latitude, Height, Longitude, Latitude, Height, ...], where Longitude and Latitude are in radians and Height is in meters.""" - def __init__(self, *, values=None): - if len(values) % 3 != 0: + values = attr.ib() + + def __attrs_post_init__(self): + if len(self.values) % 3 != 0: raise ValueError( "Invalid values. Input values should be arrays of size 3 * N" ) - self._values = values - - @property - def values(self): - return self._values - def to_json(self): - return list(self._values) + return list(self.values) +@attr.s(repr=False, frozen=True, kw_only=True) class CartographicDegreesListValue(BaseCZMLObject): """A list of geodetic, WGS84 positions specified as [Longitude, Latitude, Height, Longitude, Latitude, Height, ...], where Longitude and Latitude are in degrees and Height is in meters.""" - def __init__(self, *, values=None): - if len(values) % 3 != 0: + values = attr.ib() + + def __attrs_post_init__(self): + if len(self.values) % 3 != 0: raise ValueError( "Invalid values. Input values should be arrays of size 3 * N" ) - self._values = values - - @property - def values(self): - return self._values - def to_json(self): - return list(self._values) + return list(self.values) +@attr.s(repr=False, frozen=True, kw_only=True) class DistanceDisplayConditionValue(BaseCZMLObject): """A value indicating the visibility of an object based on the distance to the camera, specified as two values [NearDistance, FarDistance]. If the array has two elements, the value is constant. If it has three or more elements, @@ -341,23 +253,20 @@ class DistanceDisplayConditionValue(BaseCZMLObject): where Time is an ISO 8601 date and time string or seconds since epoch. """ - def __init__(self, *, values=None): - if len(values) != 2 and len(values) % 3 != 0: + values = attr.ib(default=None) + + def __attrs_post_init__(self): + if len(self.values) != 2 and len(self.values) % 3 != 0: raise ValueError( "Invalid values. Input values should be arrays of size either 2 or 3 * N" ) - self._values = values - - @property - def values(self): - return self._values - def to_json(self): - return list(self._values) + return list(self.values) -class NearFarScalarValue(BaseCZMLObject, Deletable): +@attr.s(repr=False, frozen=True, kw_only=True) +class NearFarScalarValue(BaseCZMLObject): """A near-far scalar value specified as four values [NearDistance, NearValue, FarDistance, FarValue]. If the array has four elements, the value is constant. If it has five or more elements, they are time-tagged @@ -365,78 +274,50 @@ class NearFarScalarValue(BaseCZMLObject, Deletable): FarDistance, FarValue, ...], where Time is an ISO 8601 date and time string or seconds since epoch. """ - def __init__(self, *, values=None): - if not (len(values) == 4 or len(values) % 5 == 0): + values = attr.ib(default=None) + + def __attrs_post_init__(self): + if not (len(self.values) == 4 or len(self.values) % 5 == 0): raise ValueError( "Input values must have either 4 or N * 5 values, " "where N is the number of time-tagged samples." ) - self._values = values - - @property - def values(self): - return self._values - - def to_json(self): - return list(self._values) - - -class Uri(BaseCZMLObject, Deletable): - """A URI value. - - The URI can optionally vary with time. - """ - - def __init__(self, *, delete=None, uri=None): - try: - parse_data_uri(uri) - except ValueError as e: - if not is_url(uri): - raise ValueError("uri must be a URL or a data URI") from e - - self._delete = delete - self._uri = uri - - @property - def uri(self): - """The URI value.""" - return self._uri - def to_json(self): - return self.uri + return list(self.values) +@attr.s(repr=False, frozen=True, kw_only=True) class TimeInterval(BaseCZMLObject): """A time interval, specified in ISO8601 interval format.""" - def __init__(self, *, start=None, end=None): - self._start = format_datetime_like(start) - self._end = format_datetime_like(end) + _start = attr.ib(default=None) + _end = attr.ib(default=None) def to_json(self): if self._start is None: start = "0000-00-00T00:00:00Z" else: - start = self._start + start = format_datetime_like(self._start) if self._end is None: end = "9999-12-31T24:00:00Z" else: - end = self._end + end = format_datetime_like(self._end) return "{start}/{end}".format(start=start, end=end) +@attr.s(repr=False, frozen=True, kw_only=True) class IntervalValue(BaseCZMLObject): """Value over some interval.""" - def __init__(self, *, start, end, value): - self._interval = TimeInterval(start=start, end=end) - self._value = value + _start = attr.ib() + _end = attr.ib() + _value = attr.ib() def to_json(self): - obj_dict = {"interval": self._interval} + obj_dict = {"interval": TimeInterval(start=self._start, end=self._end)} try: obj_dict.update(**self._value.to_json()) @@ -447,16 +328,17 @@ def to_json(self): return obj_dict +@attr.s(repr=False, frozen=True) class Sequence(BaseCZMLObject): """Sequence, list, array of objects.""" - def __init__(self, values): - self._values = values + _values = attr.ib() def to_json(self): return list(self._values) +@attr.s(repr=False, frozen=True, kw_only=True) class UnitQuaternionValue(_TimeTaggedCoords): """A set of 4-dimensional coordinates used to represent rotation in 3-dimensional space. diff --git a/src/czml3/utils.py b/src/czml3/utils.py index 165cde9..c26195a 100644 --- a/src/czml3/utils.py +++ b/src/czml3/utils.py @@ -2,44 +2,12 @@ def get_color(color): - # TODO: Turn this into individual classmethods on Color? # Color.from_string, Color.from_int, ... if isinstance(color, str) and 6 <= len(color) <= 10: - return get_color(int(color.rsplit("#")[-1], 16)) + return Color.from_str(color) elif isinstance(color, int): - if color > 0xFFFFFF: - values = { - "rgba": [ - (color & 0xFF000000) >> 24, - (color & 0x00FF0000) >> 16, - (color & 0x0000FF00) >> 8, - (color & 0x000000FF) >> 0, - ] - } - else: - values = { - "rgba": [ - (color & 0xFF0000) >> 16, - (color & 0x00FF00) >> 8, - (color & 0x0000FF) >> 0, - 0xFF, - ] - } - elif isinstance(color, list) and all(isinstance(v, int) for v in color): - if len(color) == 3: - values = {"rgba": color + [255]} - elif len(color) == 4: - values = {"rgba": color[:]} - else: - raise ValueError("Invalid number of values") - elif isinstance(color, list) and all(isinstance(v, float) for v in color): - if len(color) == 3: - values = {"rgbaf": color + [1.0]} - elif len(color) == 4: - values = {"rgbaf": color[:]} - else: - raise ValueError("Invalid number of values") + return Color.from_hex(color) + elif isinstance(color, list) and len(color) <= 4: + return Color.from_list(color) else: raise ValueError("Invalid input") - - return Color(**values) diff --git a/tests/test_packet.py b/tests/test_packet.py index 1240955..549e1a8 100644 --- a/tests/test_packet.py +++ b/tests/test_packet.py @@ -18,6 +18,7 @@ PolylineMaterial, Position, PositionList, + SolidColorMaterial, ) from czml3.types import Cartesian3Value, StringValue @@ -70,7 +71,7 @@ def test_packet_label(): 0.2, 0.3, 0.4, - 1 + 1.0 ] }, "outlineColor": { @@ -88,8 +89,8 @@ def test_packet_label(): id="0", label=Label( font="20px sans-serif", - fillColor=Color(rgbaf=[0.2, 0.3, 0.4, 1]), - outlineColor=Color(rgba=[0, 233, 255, 2]), + fillColor=Color.from_list([0.2, 0.3, 0.4]), + outlineColor=Color.from_list([0, 233, 255, 2]), outlineWidth=2.0, ), ) @@ -321,7 +322,7 @@ def test_packet_point(): } } }""" - packet = Packet(id="id_00", point=Point(color=Color(rgba=[255, 0, 0, 255]))) + packet = Packet(id="id_00", point=Point(color=Color.from_list([255, 0, 0, 255]))) assert repr(packet) == expected_result @@ -360,7 +361,9 @@ def test_packet_polyline(): positions=PositionList( cartographicDegrees=[-75, 43, 500000, -125, 43, 500000] ), - material=PolylineMaterial(solidColor=Color(rgba=[255, 0, 0, 255])), + material=PolylineMaterial( + solidColor=SolidColorMaterial.from_list([255, 0, 0, 255]) + ), ), ) @@ -428,7 +431,7 @@ def test_packet_polygon(): ] ), granularity=1.0, - material=Material(solidColor=Color(rgba=[255, 0, 0, 255])), + material=Material(solidColor=SolidColorMaterial.from_list([255, 0, 0])), ), ) diff --git a/tests/test_properties.py b/tests/test_properties.py index 582aba8..2bc6902 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -2,6 +2,7 @@ import pytest +from czml3.enums import ArcTypes, ClassificationTypes, ShadowModes from czml3.properties import ( ArcType, Box, @@ -25,19 +26,16 @@ ShadowMode, SolidColorMaterial, StripeMaterial, + Uri, ) from czml3.types import ( - ArcTypeValue, Cartesian3Value, CartographicDegreesListValue, - ClassificationTypeValue, DistanceDisplayConditionValue, IntervalValue, NearFarScalarValue, Sequence, - ShadowModeValue, UnitQuaternionValue, - Uri, ) @@ -102,7 +100,7 @@ def test_arc_type(): expected_result = """{ "arcType": "NONE" }""" - arc_type = ArcType(arcType=ArcTypeValue(string="NONE")) + arc_type = ArcType(arcType=ArcTypes.NONE) assert repr(arc_type) == expected_result @@ -110,7 +108,7 @@ def test_shadow_mode(): expected_result = """{ "shadowMode": "ENABLED" }""" - shadow_mode = ShadowMode(shadowMode=ShadowModeValue(string="ENABLED")) + shadow_mode = ShadowMode(shadowMode=ShadowModes.ENABLED) assert repr(shadow_mode) == expected_result @@ -145,7 +143,7 @@ def test_polyline(): distanceDisplayCondition=DistanceDisplayConditionValue(values=[14, 81]) ), classificationType=ClassificationType( - classificationType=ClassificationTypeValue(string="CESIUM_3D_TILE") + classificationType=ClassificationTypes.CESIUM_3D_TILE ), ) assert repr(pol) == expected_result @@ -164,13 +162,11 @@ def test_material_solid_color(): } } }""" - mat = Material(solidColor=SolidColorMaterial(color=Color(rgba=[200, 100, 30, 255]))) + mat = Material(solidColor=SolidColorMaterial.from_list([200, 100, 30])) assert repr(mat) == expected_result - pol_mat = PolylineMaterial( - solidColor=SolidColorMaterial(color=Color(rgba=[200, 100, 30, 255])) - ) + pol_mat = PolylineMaterial(solidColor=SolidColorMaterial.from_list([200, 100, 30])) assert repr(pol_mat) == expected_result @@ -198,7 +194,7 @@ def test_material_image(): image=ImageMaterial( image=Uri(uri="https://site.com/image.png"), repeat=[2, 2], - color=Color(rgba=[200, 100, 30, 255]), + color=Color.from_list([200, 100, 30]), ) ) assert repr(mat) == expected_result @@ -207,7 +203,7 @@ def test_material_image(): image=ImageMaterial( image=Uri(uri="https://site.com/image.png"), repeat=[2, 2], - color=Color(rgba=[200, 100, 30, 255]), + color=Color.from_list([200, 100, 30]), ) ) assert repr(pol_mat) == expected_result @@ -239,7 +235,7 @@ def test_material_grid(): }""" pol_mat = GridMaterial( - color=Color(rgba=[20, 20, 30, 255]), + color=Color.from_list([20, 20, 30]), cellAlpha=1.0, lineCount=[16, 16], lineThickness=[2.0, 2.0], @@ -272,8 +268,8 @@ def test_material_stripe(): }""" pol_mat = StripeMaterial( - evenColor=Color(rgba=[0, 0, 0, 255]), - oddColor=Color(rgba=[255, 255, 255, 255]), + evenColor=Color.from_list([0, 0, 0]), + oddColor=Color.from_list([255, 255, 255]), offset=0.3, repeat=4, ) @@ -302,8 +298,8 @@ def test_material_checkerboard(): }""" pol_mat = CheckerboardMaterial( - evenColor=Color(rgba=[0, 0, 0, 255]), - oddColor=Color(rgba=[255, 255, 255, 255]), + evenColor=Color.from_list([0, 0, 0]), + oddColor=Color.from_list([255, 255, 255]), repeat=4, ) assert repr(pol_mat) == expected_result @@ -433,3 +429,10 @@ def test_model(): ) assert repr(result) == expected_result + + +def test_bad_uri_raises_error(): + with pytest.raises(ValueError) as excinfo: + Uri(uri="a") + + assert "uri must be a URL or a data URI" in excinfo.exconly() diff --git a/tests/test_types.py b/tests/test_types.py index 7184d6c..21ab2cc 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -5,21 +5,17 @@ from dateutil.tz import tzoffset from czml3.types import ( - ArcTypeValue, Cartesian3Value, CartographicDegreesListValue, CartographicRadiansListValue, - ClassificationTypeValue, DistanceDisplayConditionValue, FontValue, NearFarScalarValue, ReferenceValue, RgbafValue, RgbaValue, - ShadowModeValue, TimeInterval, UnitQuaternionValue, - Uri, format_datetime_like, ) @@ -31,12 +27,6 @@ def test_invalid_near_far_scalar_value(): assert "Input values must have either 4 or N * 5 values, " in excinfo.exconly() -def test_classification_type(): - expected_result = '"BOTH"' - cls = ClassificationTypeValue(string="BOTH") - assert repr(cls) == expected_result - - def test_distance_display_condition(): expected_result = """[ 0, @@ -55,32 +45,6 @@ def test_distance_display_condition(): assert repr(dist) == expected_result -def test_shadow_mode(): - expected_result = '"CAST_ONLY"' - shad = ShadowModeValue(string="CAST_ONLY") - assert repr(shad) == expected_result - - -def test_invalid_shadow_mode(): - - with pytest.raises(ValueError) as excinfo: - ArcTypeValue(string="SHADOW") - assert "Invalid input value" in excinfo.exconly() - - -def test_arc_type(): - expected_result = '"RHUMB"' - arc = ArcTypeValue(string="RHUMB") - assert repr(arc) == expected_result - - -def test_invalid_arc_type(): - - with pytest.raises(ValueError) as excinfo: - ArcTypeValue(string="ARC") - assert "Invalid input value" in excinfo.exconly() - - def test_cartographic_radian_list(): expected_result = """[ 0, @@ -182,13 +146,6 @@ def test_bad_rgba_5_color_values_raises_error(): assert "Color values must be integers in the range 0-255." in excinfo.exconly() -def test_bad_uri_raises_error(): - with pytest.raises(ValueError) as excinfo: - Uri(uri="a") - - assert "uri must be a URL or a data URI" in excinfo.exconly() - - def test_bad_rgbaf_size_values_raises_error(): with pytest.raises(ValueError) as excinfo: RgbafValue(values=[0, 0, 0.1]) diff --git a/tests/test_utils.py b/tests/test_utils.py index c12e4ab..b1f0fa0 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,11 +1,12 @@ import pytest from czml3.properties import Color +from czml3.types import RgbafValue, RgbaValue from czml3.utils import get_color def test_get_color_rgba(): - expected_color = Color(rgba=[255, 204, 0, 255]) + expected_color = Color(rgba=RgbaValue(values=[255, 204, 0, 255])) # TODO: Simplify after https://github.com/poliastro/czml3/issues/36 assert get_color("#ffcc00").rgba.values == expected_color.rgba.values @@ -17,7 +18,7 @@ def test_get_color_rgba(): def test_get_color_rgbaf(): - expected_color = Color(rgbaf=[1.0, 0.8, 0.0, 1.0]) + expected_color = Color(rgbaf=RgbafValue(values=[1.0, 0.8, 0.0, 1.0])) # TODO: Simplify after https://github.com/poliastro/czml3/issues/36 assert get_color([1.0, 0.8, 0.0]).rgbaf.values == expected_color.rgbaf.values @@ -26,6 +27,5 @@ def test_get_color_rgbaf(): @pytest.mark.parametrize("input", ["a", [0, 0, 0, 0, 0], [1.0, 1.0]]) def test_get_color_invalid_input_raises_error(input): - with pytest.raises(ValueError) as exc: + with pytest.raises(ValueError): get_color(input) - assert "Invalid" in exc.exconly()