add Rotation module

This commit is contained in:
wyq 2024-05-14 09:53:04 +08:00
parent 151211d7a3
commit b4a5ea2275
15 changed files with 361 additions and 17 deletions

View File

@ -473,9 +473,13 @@ public class MapPlot extends Plot2D implements IWebMapPanel {
//Draw boundary line
if (this.boundary != null) {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
PolygonBreak pb = (PolygonBreak)this.boundary.getLegend().clone();
pb.setDrawFill(false);
this.drawGraphic(g, this.boundary, pb, area);
if (!this.antiAlias) {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
}
}
g.setTransform(oldMatrix);

View File

@ -67,7 +67,7 @@ import java.util.zip.ZipInputStream;
public static String getVersion(){
String version = GlobalUtil.class.getPackage().getImplementationVersion();
if (version == null || version.equals("")) {
version = "3.8.6";
version = "3.8.7";
}
return version;
}

View File

@ -163,8 +163,12 @@ public class DateTimeIndex extends Index<LocalDateTime> {
*/
@Override
public String getNameFormat() {
String str = this.dtFormatter.format(this.data.get(0));
return "%" + String.valueOf(str.length()) + "s";
if (this.data.isEmpty()) {
return this.format;
} else {
String str = this.dtFormatter.format(this.data.get(0));
return "%" + String.valueOf(str.length()) + "s";
}
}
// /**

View File

@ -69,6 +69,14 @@ public class Index<V> implements Iterable<V>{
public int size(){
return data.size();
}
/**
* Return if the index is empty
* @return Empty or not
*/
public boolean isEmpty() {
return this.data.isEmpty();
}
/**
* Get string format

View File

@ -1,34 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<MeteoInfo File="milconfig.xml" Type="configurefile">
<Path OpenPath="D:\Working\MIScript\Jython\mis\chart\text">
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\array"/>
<Path OpenPath="D:\Working\MIScript\Jython\mis\plot_types\funny">
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\io"/>
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\io\netcdf"/>
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\io\radar"/>
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\plot_types\3d"/>
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\plot_types\3d\jogl"/>
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\plot_types\3d\jogl\volume"/>
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\dataframe"/>
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\plot_types\scatter"/>
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\satellite"/>
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\satellite\FY"/>
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\plot_types"/>
<RecentFolder Folder="D:\Working\MIScript\Jython\mis"/>
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\chart"/>
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\chart\text"/>
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\satellite\FY"/>
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\dataframe"/>
<RecentFolder Folder="D:\Working\MIScript\Jython\mis"/>
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\plot_types"/>
<RecentFolder Folder="D:\Working\MIScript\Jython\mis\plot_types\funny"/>
</Path>
<File>
<OpenedFiles>
<OpenedFile File="D:\Working\MIScript\Jython\mis\plot_types\scatter\scatterm_grid.py"/>
<OpenedFile File="D:\Working\MIScript\Jython\mis\satellite\FY\FY4A_DSD_1.py"/>
<OpenedFile File="D:\Working\MIScript\Jython\mis\satellite\FY\FY4A_AGRI_CIX_1.py"/>
<OpenedFile File="D:\Working\MIScript\Jython\mis\chart\text\text_align_axesm.py"/>
<OpenedFile File="D:\Working\MIScript\Jython\mis\plot_types\funny\rose_multi.py"/>
</OpenedFiles>
<RecentFiles>
<RecentFile File="D:\Working\MIScript\Jython\mis\plot_types\scatter\scatterm_grid.py"/>
<RecentFile File="D:\Working\MIScript\Jython\mis\satellite\FY\FY4A_DSD_1.py"/>
<RecentFile File="D:\Working\MIScript\Jython\mis\satellite\FY\FY4A_AGRI_CIX_1.py"/>
<RecentFile File="D:\Working\MIScript\Jython\mis\chart\text\text_align_axesm.py"/>
<RecentFile File="D:\Working\MIScript\Jython\mis\plot_types\funny\rose_multi.py"/>
</RecentFiles>
</File>
<Font>
@ -36,5 +34,5 @@
</Font>
<LookFeel DockWindowDecorated="true" LafDecorated="true" Name="FlatDarkLaf"/>
<Figure DoubleBuffering="true"/>
<Startup MainFormLocation="35,0" MainFormSize="1334,786"/>
<Startup MainFormLocation="-7,0" MainFormSize="1394,829"/>
</MeteoInfo>

View File

@ -233,7 +233,11 @@ class DataFrame(object):
step = 1 if k.step is None else k.step
rowkey = Range(sidx, eidx, step)
elif isinstance(k, (list,tuple,np.NDArray,series.Series)):
if isinstance(k[0], (int, bool)):
if isinstance(k, series.Series):
k0 = k.iloc[0]
else:
k0 = k[0]
if isinstance(k0, (int, bool)):
if isinstance(k, (list, tuple)):
rowkey = k
else:

View File

@ -32,13 +32,16 @@ class Series(object):
if series is None:
if isinstance(data, (list, tuple)):
data = np.array(data)
if index is None:
index = range(0, len(data))
else:
if len(data) != len(index):
raise ValueError('Wrong length of index!')
if isinstance(index, np.NDArray):
index = index.tolist()
if isinstance(index, Index):
self._index = index
else:
@ -154,6 +157,8 @@ class Series(object):
rowkey = Range(sidx, eidx, step)
r = self._series.getValues(rowkey)
return Series(series=r)
elif isinstance(key, int):
return self._getitem_iloc(key)
else:
r = self._series.getValueByIndex(key)
if isinstance(r, MISeries):

View File

@ -1 +1,2 @@
import distance
import distance
import transform

View File

@ -0,0 +1,3 @@
from _rotation import Rotation
__all__ = ['Rotation']

View File

@ -0,0 +1,270 @@
from org.apache.commons.geometry.euclidean.threed.rotation import QuaternionRotation, AxisReferenceFrame, \
AxisSequence, AxisAngleSequence
from org.apache.commons.geometry.euclidean.threed import Vector3D
from org.meteoinfo.math.spatial.transform import TransformUtil
import re
from numbers import Number
from mipylib import numeric as np
def _cross3(a, b):
result = np.empty(3)
result[0] = a[1]*b[2] - a[2]*b[1]
result[1] = a[2]*b[0] - a[0]*b[2]
result[2] = a[0]*b[1] - a[1]*b[0]
return result
def _compose_quat_single(p, q, r):
# calculate p * q into r
cross = _cross3(p[:3], q[:3])
r[0] = p[3]*q[0] + q[3]*p[0] + cross[0]
r[1] = p[3]*q[1] + q[3]*p[1] + cross[1]
r[2] = p[3]*q[2] + q[3]*p[2] + cross[2]
r[3] = p[3]*q[3] - p[0]*q[0] - p[1]*q[1] - p[2]*q[2]
def _compose_quat(p, q):
n = max(p.shape[0], q.shape[0])
product = np.empty((n, 4))
# dealing with broadcasting
if p.shape[0] == 1:
for ind in range(n):
_compose_quat_single(p[0], q[ind], product[ind])
elif q.shape[0] == 1:
for ind in range(n):
_compose_quat_single(p[ind], q[0], product[ind])
else:
for ind in range(n):
_compose_quat_single(p[ind], q[ind], product[ind])
return product
def _make_elementary_quat(axis, angles):
n = angles.shape[0]
quat = np.zeros((n, 4))
axis_ind = 0
if axis == b'x': axis_ind = 0
elif axis == b'y': axis_ind = 1
elif axis == b'z': axis_ind = 2
for ind in range(n):
quat[ind, 3] = cos(angles[ind] / 2.)
quat[ind, axis_ind] = sin(angles[ind] / 2.)
return quat
def _elementary_quat_compose(seq, angles, intrinsic=False):
result = _make_elementary_quat(seq[0], angles[:, 0])
seq_len = seq.shape[0]
for idx in range(1, seq_len):
if intrinsic:
result = _compose_quat(
result,
_make_elementary_quat(seq[idx], angles[:, idx]))
else:
result = _compose_quat(
_make_elementary_quat(seq[idx], angles[:, idx]),
result)
return result
def _format_angles(angles, degrees, num_axes):
angles = np.asarray(angles, dtype='float')
if degrees:
angles = np.deg2rad(angles)
is_single = False
# Prepare angles to have shape (num_rot, num_axes)
if num_axes == 1:
if angles.ndim == 0:
# (1, 1)
angles = angles.reshape((1, 1))
is_single = True
elif angles.ndim == 1:
# (N, 1)
angles = angles[:, None]
elif angles.ndim == 2 and angles.shape[-1] != 1:
raise ValueError("Expected `angles` parameter to have shape "
"(N, 1), got {}.".format(angles.shape))
elif angles.ndim > 2:
raise ValueError("Expected float, 1D array, or 2D array for "
"parameter `angles` corresponding to `seq`, "
"got shape {}.".format(angles.shape))
else: # 2 or 3 axes
if angles.ndim not in [1, 2] or angles.shape[-1] != num_axes:
raise ValueError("Expected `angles` to be at most "
"2-dimensional with width equal to number "
"of axes specified, got "
"{} for shape".format(angles.shape))
if angles.ndim == 1:
# (1, num_axes)
angles = angles[None, :]
is_single = True
# By now angles should have shape (num_rot, num_axes)
# sanity check
if angles.ndim != 2 or angles.shape[-1] != num_axes:
raise ValueError("Expected angles to have shape (num_rotations, "
"num_axes), got {}.".format(angles.shape))
return angles, is_single
def _get_axis_vector(axis):
if axis == b'x': return Vector3D.of(1, 0, 0)
elif axis == b'y': return Vector3D.of(0, 1, 0)
elif axis == b'z': return Vector3D.of(0, 0, 1)
else: return Vector3D.of(1, 0, 0)
def _get_axis_sequence(seq):
if seq == 'xyz': return AxisSequence.XYZ
elif seq == 'xzy': return AxisSequence.XZY
elif seq == 'yxz': return AxisSequence.YXZ
elif seq == 'yzx': return AxisSequence.YZX
elif seq == 'zxy': return AxisSequence.ZXY
elif seq == 'zyx': return AxisSequence.ZYX
elif seq == 'xyx': return AxisSequence.XYX
elif seq == 'xzx': return AxisSequence.XZX
elif seq == 'yxy': return AxisSequence.YXY
elif seq == 'yzy': return AxisSequence.YZY
elif seq == 'zxz': return AxisSequence.ZXZ
elif seq == 'zyz': return AxisSequence.ZYZ
else: return AxisSequence.XYZ
class Rotation(object):
def __init__(self, rotation=None):
if rotation is None:
self._rotation = QuaternionRotation()
else:
self._rotation = rotation
@classmethod
def from_euler(cls, seq, angles, degrees=False):
"""Initialize from Euler angles.
Rotations in 3-D can be represented by a sequence of 3
rotations around a sequence of axes. In theory, any three axes spanning
the 3-D Euclidean space are enough. In practice, the axes of rotation are
chosen to be the basis vectors.
The three rotations can either be in a global frame of reference
(extrinsic) or in a body centred frame of reference (intrinsic), which
is attached to, and moves with, the object under rotation [1]_.
Parameters
----------
seq : string
Specifies sequence of axes for rotations. Up to 3 characters
belonging to the set {'X', 'Y', 'Z'} for intrinsic rotations, or
{'x', 'y', 'z'} for extrinsic rotations. Extrinsic and intrinsic
rotations cannot be mixed in one function call.
angles : float or array_like, shape (N,) or (N, [1 or 2 or 3])
Euler angles specified in radians (`degrees` is False) or degrees
(`degrees` is True).
For a single character `seq`, `angles` can be:
- a single value
- array_like with shape (N,), where each `angle[i]`
corresponds to a single rotation
- array_like with shape (N, 1), where each `angle[i, 0]`
corresponds to a single rotation
For 2- and 3-character wide `seq`, `angles` can be:
- array_like with shape (W,) where `W` is the width of
`seq`, which corresponds to a single rotation with `W` axes
- array_like with shape (N, W) where each `angle[i]`
corresponds to a sequence of Euler angles describing a single
rotation
degrees : bool, optional
If True, then the given angles are assumed to be in degrees.
Default is False.
Returns
-------
rotation : `Rotation` instance
Object containing the rotation represented by the sequence of
rotations around given axes with given angles.
"""
num_axes = len(seq)
if num_axes < 1 or num_axes > 3:
raise ValueError("Expected axis specification to be a non-empty "
"string of upto 3 characters, got {}".format(seq))
intrinsic = (re.match(r'^[XYZ]{1,3}$', seq) is not None)
extrinsic = (re.match(r'^[xyz]{1,3}$', seq) is not None)
if not (intrinsic or extrinsic):
raise ValueError("Expected axes from `seq` to be from ['x', 'y', "
"'z'] or ['X', 'Y', 'Z'], got {}".format(seq))
if any(seq[i] == seq[i+1] for i in range(num_axes - 1)):
raise ValueError("Expected consecutive axes to be different, "
"got {}".format(seq))
seq = seq.lower()
angles = np.asarray(angles, dtype='float')
if degrees:
angles = np.deg2rad(angles)
if angles.ndim == 0:
axis_vector = _get_axis_vector(seq)
rotation = QuaternionRotation.fromAxisAngle(axis_vector, angles.item())
else:
arf = AxisReferenceFrame.RELATIVE if intrinsic else AxisReferenceFrame.ABSOLUTE
axis_sequence = _get_axis_sequence(seq)
aas = AxisAngleSequence(arf, axis_sequence, angles[0], angles[1], angles[2])
rotation = QuaternionRotation.fromAxisAngleSequence(aas)
return cls(rotation)
def apply(self, vectors, inverse=False):
"""Apply this rotation to a set of vectors.
If the original frame rotates to the final frame by this rotation, then
its application to a vector can be seen in two ways:
- As a projection of vector components expressed in the final frame
to the original frame.
- As the physical rotation of a vector being glued to the original
frame as it rotates. In this case the vector components are
expressed in the original frame before and after the rotation.
In terms of rotation matrices, this application is the same as
``self.as_matrix() @ vectors``.
Parameters
----------
vectors : array_like, shape (3,) or (N, 3)
Each `vectors[i]` represents a vector in 3D space. A single vector
can either be specified with shape `(3, )` or `(1, 3)`. The number
of rotations and number of vectors given must follow standard numpy
broadcasting rules: either one of them equals unity or they both
equal each other.
inverse : boolean, optional
If True then the inverse of the rotation(s) is applied to the input
vectors. Default is False.
Returns
-------
rotated_vectors : ndarray, shape (3,) or (N, 3)
Result of applying rotation on input vectors.
Shape depends on the following cases:
- If object contains a single rotation (as opposed to a stack
with a single rotation) and a single vector is specified with
shape ``(3,)``, then `rotated_vectors` has shape ``(3,)``.
- In all other cases, `rotated_vectors` has shape ``(N, 3)``,
where ``N`` is either the number of rotations or vectors.
"""
vectors = np.asarray(vectors)
if vectors.ndim > 2 or vectors.shape[-1] != 3:
raise ValueError("Expected input of shape (3,) or (P, 3), "
"got {}.".format(vectors.shape))
r = TransformUtil.rotation(self._rotation, vectors._array)
return np.NDArray(r)

View File

@ -15,6 +15,7 @@
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<ejml.version>0.41.1</ejml.version>
<commons-geometry.version>1.0</commons-geometry.version>
</properties>
<dependencies>
@ -43,6 +44,16 @@
<artifactId>ojalgo</artifactId>
<version>52.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-geometry-core</artifactId>
<version>${commons-geometry.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-geometry-euclidean</artifactId>
<version>${commons-geometry.version}</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp-platform</artifactId>

View File

@ -0,0 +1,36 @@
package org.meteoinfo.math.spatial.transform;
import org.apache.commons.geometry.core.Transform;
import org.apache.commons.geometry.euclidean.threed.Vector3D;
import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
import org.meteoinfo.ndarray.Array;
import org.meteoinfo.ndarray.DataType;
public class TransformUtil {
/**
* Quaternion rotation of 3D axis
* @param quaternionRotation The rotation
* @param array Input array
* @return Rotated array
*/
public static Array rotation(QuaternionRotation quaternionRotation, Array array) {
array = array.copyIfView();
if (array.getRank() == 1) {
Vector3D v1 = Vector3D.of(array.getDouble(0), array.getDouble(1), array.getDouble(2));
Vector3D v2 = quaternionRotation.apply(v1);
return Array.factory(DataType.DOUBLE, array.getShape(), v2.toArray());
}
int[] shape = array.getShape();
int ni = shape[0];
int nj = shape[1];
Array r = Array.factory(DataType.DOUBLE, array.getShape());
for (int i = 0; i < array.getSize(); i += 3) {
Vector3D v1 = Vector3D.of(array.getDouble(i), array.getDouble(i + 1), array.getDouble(i + 2));
Vector3D v2 = quaternionRotation.apply(v1);
r.setDouble(i, v2.getX());
r.setDouble(i + 1, v2.getY());
r.setDouble(i + 2, v2.getZ());
}
return r;
}
}

View File

@ -35,7 +35,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<revision>3.8.6</revision>
<revision>3.8.7</revision>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.compiler.release>8</maven.compiler.release>