Skip to content

Commit

Permalink
v0.9.4, small improvements of taxonomy
Browse files Browse the repository at this point in the history
  • Loading branch information
biodiv committed May 13, 2022
1 parent cf3c49d commit d755abc
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 41 deletions.
2 changes: 1 addition & 1 deletion localcosmos_server/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
name = 'localcosmos_server'
__version__ = '0.9.3'
__version__ = '0.9.4'
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,17 @@ class InteractiveImageField {
"greyAreaFillColor" : "rgba(0,0,0,0.5)",
"relativeArrowStrokeWidth" : 0.02,
"relativeArrowLength" : 0.5,
"defaultArrowColor" : "#000000"
"defaultArrowColor" : "#000000",
"cropMode" : "contains", // contained: crop area contained inside image, contains: crop area contains image (can be larger than image)
"stageBackground" : "#FFFFFF" // only used if cropMode == "contains"
};

for (let key in options){
this.options[key] = options[key];
}

this.stage = null;
this.konvaImage = null;
this.cropArea = null;
this.cropAreaGrey = null;
this.image = null;
Expand Down Expand Up @@ -125,16 +128,22 @@ class InteractiveImageField {
this.container.prepend(this.imageContainer);
}

/*
* The origin (0/0) of the coordinate system which the crop paramters refer to is the top left corner of the image
* this way, the stage can be altered without compromising the validity of the crop parameters
*/
setCropParametersInputValue (){
if (this.cropArea != null){

let reverseScalingFactor = 1 / this.getScalingFactor();

let cropAreaRect = this.cropArea.getClientRect();

let imageRect = this.konvaImage.getClientRect();

var data = {
"x" : Math.round(cropAreaRect.x * reverseScalingFactor),
"y" : Math.round(cropAreaRect.y * reverseScalingFactor),
"x" : Math.round( (cropAreaRect.x - imageRect.x) * reverseScalingFactor),
"y" : Math.round( (cropAreaRect.y - imageRect.y) * reverseScalingFactor),
"width" : Math.round(cropAreaRect.width * reverseScalingFactor),
"height" : Math.round(cropAreaRect.height * reverseScalingFactor),
"rotate" : 0,
Expand Down Expand Up @@ -225,46 +234,85 @@ class InteractiveImageField {

}


addStage () {

let originalHeight = this.image.height;
let stageWidth = this.imageContainer.offsetWidth;
let stageHeight = stageWidth;

let scalingFactor = this.getScalingFactor();
if (this.options.cropMode == "contained"){

if (this.DEBUG == true){
console.log("[InteractiveImageField] loading stage. image height: " + this.image.height + " , image width : " + this.image.width + " scalingFactor: " + scalingFactor);
}
let originalHeight = this.image.height;

let imageCanvasWidth = this.imageContainer.offsetWidth;
let imageCanvasHeight = parseInt(originalHeight * scalingFactor);
let scalingFactor = this.getScalingFactor();

if (this.stage != null){
this.stage.destroy();
}
if (this.DEBUG == true){
console.log("[InteractiveImageField] loading stage. image height: " + this.image.height + " , image width : " + this.image.width + " scalingFactor: " + scalingFactor);
}

stageHeight = parseInt(originalHeight * scalingFactor);

if (this.DEBUG == true){
console.log("instantiating Konva.Stage width: " + imageCanvasWidth + " height: " + imageCanvasHeight);

if (this.DEBUG == true){
console.log("instantiating Konva.Stage width: " + stageWidth + " height: " + stageHeight);
}
}

if (this.stage != null){
this.stage.destroy();
}

this.stage = new Konva.Stage({
container: this.imageContainer.id, // id of container <div>
width: imageCanvasWidth,
height: imageCanvasHeight
width: stageWidth,
height: stageHeight
});
}

// if the image contains the crop area, the image has the same size like the stage
// if the crop area contains the image (crop area can extend image), only one dimension (height or width) fits into the stage and equals the stage
addKonvaImage () {

let konvaImage = new Konva.Image({
// in "contained" mode, the image dimensions equal the stage dimensions
let imageWidth = this.stage.width();
let imageHeight = this.stage.height();
let offsetX = 0;
let offsetY = 0;

// in "contains" mode, the stage is square and the image fits into the stage
if (this.options.cropMode == "contains") {
// crop area contains image

let scalingFactor = this.getScalingFactor();

imageWidth = this.image.width * scalingFactor;
imageHeight = this.image.height * scalingFactor;

offsetX = ( this.stage.width() / 2 ) - ( imageWidth / 2)
offsetY = ( this.stage.height() / 2 ) - ( imageHeight / 2 )
}

this.konvaImage = new Konva.Image({
x: offsetX,
y: offsetY,
image: this.image,
width: imageWidth,
height: imageHeight
});

let imageLayer = new Konva.Layer();

let background = new Konva.Rect({
x: 0,
y: 0,
image: this.image,
width: this.stage.width(),
height: this.stage.height()
height: this.stage.height(),
fill : this.options.stageBackground
});

let imageLayer = new Konva.Layer();
imageLayer.add(konvaImage);
imageLayer.add(background);

imageLayer.add(this.konvaImage);

this.stage.add(imageLayer);

Expand Down Expand Up @@ -308,6 +356,7 @@ class InteractiveImageField {

let scalingFactor = this.getScalingFactor()

// 1 pixel tolerance
let initialCropSquareLength = Math.min(this.stage.width(), this.stage.height());

// add the crop area
Expand Down Expand Up @@ -336,8 +385,8 @@ class InteractiveImageField {

cropParameters = {

x : cropParametersInputValue.x * scalingFactor,
y : cropParametersInputValue.y * scalingFactor,
x : ( cropParametersInputValue.x * scalingFactor) + this.konvaImage.x(),
y : ( cropParametersInputValue.y * scalingFactor) + this.konvaImage.y(),
width : cropSquareLength,
height: cropSquareLength

Expand Down Expand Up @@ -385,15 +434,23 @@ class InteractiveImageField {
enabledAnchors :['top-left','top-right', 'bottom-left', 'bottom-right'],
shouldOverdrawWholeArea : true,
boundBoxFunc: (oldBox, newBox) => {

const box = this.getClientRect(newBox);
const isOut =
box.x < 0 ||
box.y < 0 ||
box.x + box.width > this.stage.width() ||
box.y + box.height > this.stage.height();
Math.round(box.x + box.width) > this.stage.width() ||
Math.round(box.y + box.height) > this.stage.height();
// if new bounding box is out of visible viewport, let's just skip transforming
// this logic can be improved by still allow some transforming if we have small available space
// apply Math.round() to prevent scaling the rect resulting box.x + box.width in something like 300.00000003 while stage.width() being 300
if (isOut) {

if (this.DEBUG == true){
console.log(box.x + box.width + " VS stage " + this.stage.width())
console.log("[InteractiveImageField] isOut, using old box");
}

return oldBox;
}
return newBox;
Expand Down Expand Up @@ -1095,19 +1152,39 @@ class InteractiveImageField {
}

// helper methods
// the scaling factor describes the scaling of the image, which is necessary to fit it into the stage
// if cropArea contains the image, the larger dimension of width/height defines the scaling factor
getScalingFactor () {

if (this.DEBUG == true){
console.log("[InteractiveImageField] calculating scaling factor. container.offsetWidth: " + this.container.offsetWidth + " stage: " + this.stage + " image width: " + this.image.width);
}

let scalingFactor = null;

let stageWidth = this.container.offsetWidth;

if (this.stage != null){
stageWidth = this.stage.width();
}

return stageWidth / this.image.width;
if (this.options.cropMode == "contains"){
// stage is present
let stageHeight = this.stage.height();


if (this.image.height >= this.image.width){
scalingFactor = stageHeight / this.image.height;
}
else {
scalingFactor = stageWidth / this.image.width;
}
}
else {
scalingFactor = stageWidth / this.image.width;
}

return scalingFactor;
}

getClientRect(rotatedBox) {
Expand Down
42 changes: 30 additions & 12 deletions localcosmos_server/taxonomy/lazy.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
from urllib.parse import quote_plus
from http.client import RemoteDisconnected

from django.conf import settings

SUPPORTED_LAZY_TAXONOMY_SOURCES = [db[0] for db in settings.LAZY_TAXONOMY_SOURCES]

##################################################################################################################
#
# LazyTaxon
Expand Down Expand Up @@ -34,20 +38,12 @@ def __init__(self, *args, **kwargs):

self.origin = self.instance.__class__.__name__

# if the instance has an attribute taxon_source it derives from ModelWithTaxonCommon
# if the instance has an attribute source_id it derives from the DB - might be deprecated
taxon_source = self.get_taxon_source(self.instance)

if hasattr(self.instance, 'taxon_source'):
self.taxon_source = self.instance.taxon_source
if not taxon_source:
raise ValueError('Non-taxonomic instance passed to LazyTaxon. Could not determine taxon_source.')

# in this case, it is a taxon directly from the taxonomic database
elif hasattr(self.instance, 'source_id'):
# remove ".models" from module
self.taxon_source = ('.').join(self.instance.__module__.split('.')[:-1])
#self.taxon_source = 'taxonomy.sources.%s' % self.instance._meta.app_label

else:
raise ValueError('Non-taxonomic instance passed to LazyTaxon')
self.taxon_source = taxon_source


elif 'taxon_latname' in kwargs and 'taxon_author' in kwargs and 'taxon_source' in kwargs and 'taxon_nuid' in kwargs and 'name_uuid' in kwargs:
Expand All @@ -63,6 +59,28 @@ def __init__(self, *args, **kwargs):
raise ValueError('Unable to instantiate LazyTaxon, improper parameters given: %s' %kwargs)


def get_taxon_source(self, instance):

taxon_source = None

# if the instance has an attribute taxon_source it derives from ModelWithTaxonCommon
# if the instance has an attribute source_id it derives from the DB - might be deprecated

if hasattr(instance, 'taxon_source'):
taxon_source = instance.taxon_source

# in this case, it is a taxon directly from the taxonomic database
else:
# remove ".models" from module
taxon_source = ('.').join(instance.__module__.split('.')[:-1])
#self.taxon_source = 'taxonomy.sources.%s' % instance._meta.app_label

if taxon_source not in SUPPORTED_LAZY_TAXONOMY_SOURCES:
raise ValueError('unsupported taxonomic source passed to LazyTaxon: {0}'.format(taxon_source))

return taxon_source


def gbif_nubKey(self):

gbif_nubKey = None
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

setup(
name='localcosmos_server',
version='0.9.3',
version='0.9.4',
description='LocalCosmos Private Server. Run your own server for localcosmos.org apps.',
long_description=long_description,
long_description_content_type="text/markdown",
Expand Down

0 comments on commit d755abc

Please sign in to comment.