-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfarm_fence.py
More file actions
127 lines (115 loc) · 4.44 KB
/
Copy pathfarm_fence.py
File metadata and controls
127 lines (115 loc) · 4.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
from functools import lru_cache
from pyproj import Transformer
from shapely.ops import transform as shapely_transform
from odoo import api, fields, models
# US survey foot in meters. Used to convert EPSG:5070 length (m) to feet
# for display — US farm fencing is universally specified in feet.
M_PER_US_SURVEY_FOOT = 0.3048006096012192
# Selection keys are descriptive strings (good/fair/repair) for the UI, but
# alpha-DESC would order "repair > good > fair" — fair-condition fences
# would hide below good-condition ones. `condition_rank` mirrors the
# selection so `_order` produces the actual urgency sort.
_CONDITION_RANK = {"good": 0, "fair": 1, "repair": 2}
@lru_cache(maxsize=1)
def _wgs84_to_albers_conus():
"""Build the EPSG:4326 → EPSG:5070 transformer once and reuse it.
Same approach farm_field_geo uses for acreage — Albers reprojection
gives a length number that matches what NRCS reports for the same
fence in a rangeland-improvement filing.
"""
return Transformer.from_crs("EPSG:4326", "EPSG:5070", always_xy=True).transform
class FarmFence(models.Model):
_name = "farm.fence"
_description = "Fence"
_inherit = ["mail.thread"]
# Repairs-needed bubble to the top.
_order = "condition_rank desc, name"
# Enforces field_id.company_id == fence.company_id at write time (only
# checked when field_id is set; perimeter fences with a null field_id
# pass through).
_check_company_auto = True
name = fields.Char(required=True, tracking=True)
active = fields.Boolean(
default=True,
help="Untick to archive fences that have been removed or replaced. "
"Their geometry and history stay on the field map's archived view.",
)
field_id = fields.Many2one(
"farm.field",
string="Primary Field",
help="Field this fence belongs to. Leave blank for perimeter fences "
"that span multiple fields.",
ondelete="set null",
check_company=True,
)
geom = fields.GeoLine(
string="Fence Line",
srid=4326,
help="Polyline tracing the fence. Length and the map view come from "
"this geometry. base_geoengine 19.0 names the type GeoLine (no "
"'String' suffix).",
)
length_feet = fields.Float(
compute="_compute_length_feet",
store=True,
digits=(10, 1),
help="Auto-computed from the polyline length, reprojected to "
"EPSG:5070 (Albers Equal Area) and converted from meters to US "
"survey feet.",
)
fence_type = fields.Selection(
[
("barbed_wire", "Barbed Wire"),
("high_tensile", "High-Tensile"),
("electric", "Electric"),
("post_rail", "Post & Rail"),
("woven_wire", "Woven Wire"),
("polywire", "Polywire (rotational)"),
("temporary", "Temporary"),
],
required=True,
tracking=True,
)
height_inches = fields.Float(digits=(5, 1))
condition = fields.Selection(
[
("good", "Good"),
("fair", "Fair"),
("repair", "Needs Repair"),
],
default="good",
required=True,
tracking=True,
)
condition_rank = fields.Integer(
compute="_compute_condition_rank",
store=True,
index=True,
help="Numeric mirror of condition so _order produces an urgency sort "
"(string DESC on selection keys would put 'good' above 'fair').",
)
last_checked_date = fields.Date(tracking=True)
notes = fields.Text()
company_id = fields.Many2one(
"res.company",
required=True,
default=lambda self: self.env.company,
index=True,
)
@api.depends("condition")
def _compute_condition_rank(self):
for rec in self:
rec.condition_rank = _CONDITION_RANK.get(rec.condition, 0)
@api.depends("geom")
def _compute_length_feet(self):
# base_geoengine returns the field as a plain shapely geometry on
# read; shapely has no .transform() method. Reproject via pyproj +
# shapely.ops.transform before measuring length. Mirrors the
# acreage compute in farm_field_geo.
transformer = _wgs84_to_albers_conus()
for rec in self:
if not rec.geom:
rec.length_feet = 0.0
continue
projected = shapely_transform(transformer, rec.geom)
rec.length_feet = projected.length / M_PER_US_SURVEY_FOOT