# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
# SPDX-License-Identifier: MPL-2.0
from typing import List, Tuple, Union
import numpy as np
import pandas as pd
from VeraGridEngine.Devices.Parents.editable_device import EditableDevice
from VeraGridEngine.enumerations import DeviceType
[docs]
class LineLocation(EditableDevice):
"""
Line location object
"""
def __init__(self,
lat: float,
lon: float,
z: float,
seq: int = 0,
name: str = "",
idtag: Union[str, None] = None,
code: str = ""):
"""
Constructor
:param lat: Latitude (deg)
:param lon: Longitude (deg)
:param z: Altitude (m)
:param seq: Sequential position
:param name: Name
:param idtag: unique identifyer
:param code: secondary code
"""
EditableDevice.__init__(self,
name=name,
idtag=idtag,
code=code,
device_type=DeviceType.LineLocation)
self.locations = None
self.lat = lat
self.long = lon
self.alt = z
self.seq = seq
def __eq__(self, other: "LineLocation") -> bool:
"""
Compare two
:param other: LineLocation
:return: bool
"""
return (np.isclose(self.lat, other.lat) and
np.isclose(self.long, other.long) and
np.isclose(self.alt, other.alt) and
np.isclose(self.seq, other.seq))
[docs]
def copy(self, forced_new_idtag: bool = False):
return LineLocation(
lat=self.lat,
lon=self.long,
z=self.alt,
seq=self.seq,
name=self.name,
code=self.code,
idtag=None if forced_new_idtag else self.idtag,
)
[docs]
class LineLocations(EditableDevice):
"""
LineLocations
"""
__slots__ = (
'data'
)
def __init__(self,
name: str = "",
idtag: Union[str, None] = None,
code: str = ""):
"""
Constructor
:param name: Name
:param idtag: unique identifier
:param code: secondary code
"""
EditableDevice.__init__(self,
name=name,
idtag=idtag,
code=code,
device_type=DeviceType.LineLocations)
self.data: List[LineLocation] = list()
[docs]
def get_locations(self) -> List[LineLocation]:
"""
Get list of LineLocation
"""
return self.data
[docs]
def add(self, sequence: int, latitude: float, longitude: float, altitude: float = 0.0, idtag: str = ""):
"""
Append row to this object (very slow)
:param sequence: Sequence of the point
:param latitude: Latitude (deg)
:param longitude: Longitude (deg)
:param altitude: Altitude (m)
:param idtag: Known idtag
"""
self.data.append(LineLocation(lat=latitude, lon=longitude, z=altitude, seq=sequence, idtag=idtag))
[docs]
def add_location(self, lat: float, long: float, alt: float = 0.0):
"""
Add a location to the line
:param lat: Latitude (deg)
:param long: Longitude (deg)
:param alt: Altitude (m)
"""
sequence = len(self.data)
self.add(sequence=sequence, latitude=lat, longitude=long, altitude=alt)
[docs]
def remove(self, loc: LineLocation):
self.data.remove(loc)
[docs]
def parse(self, data: List[Union[Tuple[int, float, float, float], Tuple[int, float, float, float, str]]]):
"""
Parse Json data
:param data: List of lists with (latitude, longitude, altitude)
"""
if len(data) > 0:
values = np.array(data)
self.set(data=values)
else:
self.data = list()
[docs]
def copy(self, forced_new_idtag: bool = False):
"""
:param forced_new_idtag:
:return:
"""
ll = LineLocations(name=self.name, idtag=self.idtag, code=self.code)
ll.data = [dta.copy(forced_new_idtag) for dta in self.data]
return ll
[docs]
def set(self, data: np.ndarray):
"""
Parse Json data
:param data: List of lists with (sequence, latitude, longitude, altitude)
"""
if data.ndim == 2:
if data.shape[1] == 4:
self.data.clear()
for sequence, latitude, longitude, altitude in data:
self.data.append(LineLocation(lat=float(latitude),
lon=float(longitude),
z=float(altitude),
seq=int(float(sequence))))
elif data.shape[1] == 5:
self.data.clear()
for sequence, latitude, longitude, altitude, idtag in data:
self.data.append(LineLocation(lat=float(latitude),
lon=float(longitude),
z=float(altitude),
seq=int(float(sequence)),
idtag=str(idtag)))
else:
raise ValueError('Locations data does not have exactly 3 columns')
else:
raise ValueError('Location data must be 2-dimensional: (n_points, 3)')
[docs]
def to_list(self) -> List[Tuple[int, float, float, float, str]]:
"""
Convert data to list of lists for Json usage
:return: List[Tuple[int, float, float, float]] -> [(sequence, latitude, longitude, altitude)]
"""
return [(loc.seq, loc.lat, loc.long, loc.alt, loc.idtag) for loc in self.data]
[docs]
def to_df(self) -> pd.DataFrame:
"""
Convert data to DataFrame
:return: DataFrame
"""
return pd.DataFrame(data=self.to_list(), columns=["sequence", "latitude", "longitude", "altitude", "idtag"])
def __eq__(self, other: "LineLocations") -> bool:
"""
Equality compare
In this case we check that the numerical data is close enough
:param other: LineLocations object
:return: Close enough?
"""
if len(self.data) != len(other.data):
return False
for a, b in zip(self.data, other.data):
if a != b:
return False
return True
def __str__(self) -> str:
return f"{len(self.data)} locations"