/**
* OpenSeadragon paperjs overlay plugin based on paper.js
* @version 0.4.13
*
* Includes additional open source libraries which are subject to copyright notices
* as indicated accompanying those segments of code.
*
* Original code:
* Copyright (c) 2022-2024, Thomas Pearce
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of osd-paperjs-annotation nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
import { AnnotationItem } from "./annotationitem.mjs";
import { paper } from '../paperjs.mjs';
/**
* Represents an ellipse annotation item.
* @class
* @memberof OSDPaperjsAnnotation
* @extends AnnotationItem
* @description The `Ellipse` class represents an ellipse annotation item. It inherits from the `AnnotationItem` class and provides methods to work with ellipse annotations.
*/
class Ellipse extends AnnotationItem{
/**
* Create a new Ellipse instance.
* @param {Object} geoJSON - The GeoJSON object containing annotation data.
* @throws {string} Throws an error if the GeoJSON type or subtype is invalid.
* @property {paper.CompoundPath} _paperItem - The associated paper item representing the ellipse.
* @description This constructor initializes a new ellipse annotation item based on the provided GeoJSON object.
*/
constructor(geoJSON){
super(geoJSON);
if (geoJSON.geometry.type !== 'Point' || geoJSON.geometry.properties.subtype !== 'Ellipse') {
error('Bad geoJSON object: type !=="Point" or subtype !=="Rectangle"');
}
let poly = new paper.CompoundPath({
children: [],
fillRule: 'evenodd',
});
if(geoJSON.geometry.coordinates.length > 1){
let center = geoJSON.geometry.coordinates.slice(0, 2);
let x = center[0] || 0;
let y = center[1] || 0;
let props = geoJSON.geometry.properties;
let a = props.majorRadius || 0;
let b = props.minorRadius || 0;
let degrees = props.angle || 0;
let ellipse = new paper.Path.Ellipse({
center: new paper.Point(x, y),
radius: new paper.Size(a, b)
})
poly.addChild(ellipse);
poly.rotate(degrees);
}
poly.canBeBoundingElement = true;
this.paperItem = poly;
}
/**
* Retrieves the supported types by the Ellipse annotation item.
* @static
* @param { String } type
* @param { String } [subtype]
* @returns {Boolean} Whether this constructor supports the requested type/subtype
*/
static supportsGeoJSONType(type, subtype){
return type.toLowerCase() === 'point' && subtype?.toLowerCase() === 'ellipse';
}
/**
* Get the type of this object.
* @returns { Object } with fields `type === 'Point'` and `subtype === 'Ellipse'`
*/
getGeoJSONType(){
return {
type: 'Point',
subtype: 'Ellipse'
}
}
/**
* Retrieves the coordinates of the center of the ellipse.
* @returns {Array} An array containing the x and y coordinates of the center.
* @description This method returns an array of coordinates representing the position of the center of the ellipse.
*/
getCoordinates(){
let item = this.paperItem;
return [item.position.x, item.position.y];
}
/**
* Retrieves the properties of the ellipse.
* @returns {Object} The properties object.
* @description This method returns the properties associated with the ellipse.
*/
getProperties(){
let item = this.paperItem;
let path = item.children[0];
let points = path.segments.map(s=>s.point);
let ax1 = points[2].subtract(points[0]);
let ax2 = points[3].subtract(points[1]);
let a, b;
if(ax1.length > ax2.length){
a = ax1;
b = ax2;
} else {
a = ax2;
b = ax1;
}
let angle = a.angle;
return {
majorRadius: a.length/2,
minorRadius: b.length/2,
angle: angle
};
}
/**
* Handle transformation operations on the ellipse item.
* @static
* @param {...string} operation - The transformation operation.
* @description This static method handles transformation operations on the ellipse item, such as rotation.
*/
static onTransform(){
let operation = arguments[0];
switch(operation){
case 'complete':{
let curves = this.children[0].curves;
let center = this.bounds.center;
//take two adjacent curves (of the four total) and find the point on each closest to the center
let nearpoints = curves.slice(0, 2).map(curve=>{
return {
curve: curve,
location: curve.getNearestLocation(center),
}
}).sort((a,b) => a.location.distance - b.location.distance);
let closest = nearpoints[0].location.point;
if(closest.equals(nearpoints[0].curve.segment1.point) || closest.equals(nearpoints[0].curve.segment2.point)){
//no recalculation of points/axes required, the nearest point is already one of our existing points, just return
return;
}
let t = nearpoints[0].location.curve == nearpoints[0].curve ? nearpoints[0].location.time : 1;//if owned by the other curve, time == 1 by definition
let b = closest.subtract(center);//minor axis
let a = nearpoints[1].curve.getLocationAtTime(t).point.subtract(center);//major axis
let ellipse = new paper.Path.Ellipse({center:center, radius: [a.length, b.length]}).rotate(a.angle);
this.children[0].set({segments: ellipse.segments});
break;
}
}
}
}
export{Ellipse};