frontend/src/models/Field.ts

205 lines
6.1 KiB
TypeScript

import { cloneDeep } from "lodash";
import { pond } from "protobuf-ts/pond";
import { or } from "utils/types";
import area from "@turf/area";
import { Feature } from "geojson";
import { GeometryMapping } from "./GeometryMapping";
import GrainDescriber from "grain/GrainDescriber";
import { LngLat, LngLatBounds } from "mapbox-gl";
export class Field {
public settings: pond.FieldSettings = pond.FieldSettings.create();
public status: pond.FieldStatus = pond.FieldStatus.create();
public permissions: pond.Permission[] = [];
public static create(pf?: pond.Field): Field {
let my = new Field();
if (pf) {
//the from object is required here to deal with a protobuf quirk where enums would be seen as strings at runtime, but the code sees them as the actual value
let field = pond.Field.fromObject(cloneDeep(or(pf, pond.Field.create())))
my.settings = field.settings ? field.settings : pond.FieldSettings.create()
my.status = field.status ? field.status : pond.FieldStatus.create()
my.permissions = field.fieldPermissions
}
return my;
}
public static clone(other?: Field): Field {
if (other) {
return Field.create(
pond.Field.fromObject({
settings: cloneDeep(other.settings),
status: cloneDeep(other.status),
permissions: cloneDeep(other.permissions)
})
);
}
return Field.create();
}
public calculateAcres(): number {
let totalAcres = 0;
const metersToAcresConversionValue = 0.00024710538146717;
let geoData = this.settings.fieldGeoData;
if (geoData) {
//round to 2 decimal places
totalAcres =
Math.round(
area(
GeometryMapping.geoJSON(geoData.geoShape, geoData.shapes, geoData.holes) as Feature
) *
metersToAcresConversionValue *
100
) / 100;
}
return totalAcres;
}
public static any(data: any): Field {
return Field.create(pond.Field.fromObject(cloneDeep(data)));
}
public key(): string {
return this.settings.key;
}
public fieldName(): string {
return this.settings.fieldName;
}
public landLoc(): string {
return this.settings.landLocation;
}
public crop(): pond.Grain {
return this.settings.crop;
}
public name(): string {
return this.settings.fieldName;
}
public objectType(): pond.ObjectType {
return pond.ObjectType.OBJECT_TYPE_FIELD;
}
public objectTypeString(): string {
return "Field";
}
public grain(): pond.Grain {
return this.settings.crop;
}
public subtype(): string {
return this.settings.grainSubtype;
}
public customType(): string {
let c = "";
if (this.settings) {
c = this.settings.customGrain;
}
return c;
}
public acres(): number {
if (this.settings.acres > 0) {
return this.settings.acres;
} else {
return this.calculateAcres();
}
}
//these are for when using the field as a source for a grain transaction to compare if the destination matches
public storage(): pond.BinStorage {
let storage = pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN;
if (this.settings.crop === pond.Grain.GRAIN_CUSTOM) {
storage = pond.BinStorage.BIN_STORAGE_UNSUPPORTED_GRAIN;
}
return storage;
}
/**
* returns the bushels per tonne set in the bins settings, if not set will return 1
* @returns 1 or bushels per tonne
*/
public bushelsPerTonne(): number {
//trying to avoid a divide by 0 error by only returning a value greater than 0
//since to get the weight you divide the current bushels by the bushels per tonne
let bpt = 1;
if (this.settings.bushelsPerTonne > 0) {
bpt = this.settings.bushelsPerTonne;
}
return bpt;
}
public center(): pond.Coordinates {
let coords = pond.Coordinates.create();
if (this.settings.fieldGeoData) {
let minLong = 0;
let minLat = 0;
let maxLong = 0;
let maxLat = 0;
this.settings.fieldGeoData.shapes.forEach(shape => {
shape.points.forEach(pair => {
if (pair.longitude < minLong || minLong === 0) minLong = pair.longitude;
if (pair.longitude > maxLong || maxLong === 0) maxLong = pair.longitude;
if (pair.latitude < minLat || minLat === 0) minLat = pair.latitude;
if (pair.latitude > maxLat || maxLat === 0) maxLat = pair.latitude;
});
});
coords.longitude = (maxLong + minLong) / 2;
coords.latitude = (maxLat + minLat) / 2;
}
return coords;
}
/**
* Returns a LngLatBounds object containing the southwest and northeast corners of a box to contain the field, spacing can also be provided
* to pad the sides of the bounding area
* @param spacing - an optional paramater that will expand the bounding area by the given long lat amount
* @returns LngLatBounds - an object containing the southwest and northeast coordinates of a bounding box
*/
public fieldBounds(spacing?: number): LngLatBounds {
let minLong = 0;
let minLat = 0;
let maxLong = 0;
let maxLat = 0;
if (this.settings.fieldGeoData) {
this.settings.fieldGeoData.shapes.forEach(shape => {
shape.points.forEach(pair => {
if (pair.longitude < minLong || minLong === 0) minLong = pair.longitude;
if (pair.longitude > maxLong || maxLong === 0) maxLong = pair.longitude;
if (pair.latitude < minLat || minLat === 0) minLat = pair.latitude;
if (pair.latitude > maxLat || maxLat === 0) maxLat = pair.latitude;
});
});
}
// let southWest = [minLong,minLat]
// let northEast = [maxLong,maxLat]
if(spacing){
minLong = minLong - spacing
minLat = minLat - spacing
maxLong = maxLong + spacing
maxLat = maxLat + spacing
}
return new LngLatBounds(
new LngLat(minLong, minLat),
new LngLat(maxLong, maxLat)
);
// return[southWest, northEast]
}
public grainName(): string {
if (this.grain() !== pond.Grain.GRAIN_INVALID) {
if (this.grain() === pond.Grain.GRAIN_CUSTOM) {
return this.customType();
} else {
return GrainDescriber(this.grain()).name;
}
} else {
return "None";
}
}
}