146 lines
5.1 KiB
Python
146 lines
5.1 KiB
Python
import json
|
|
from urllib.parse import quote
|
|
|
|
from odoo import api, fields, models
|
|
from odoo.exceptions import ValidationError
|
|
|
|
|
|
class RepairFsmZone(models.Model):
|
|
_name = "repair.fsm.zone"
|
|
_description = "FSM Zone"
|
|
_order = "name"
|
|
_inherit = ["mail.thread", "mail.activity.mixin"]
|
|
|
|
name = fields.Char(required=True, tracking=True)
|
|
code = fields.Char(tracking=True)
|
|
polygon_geojson = fields.Text(
|
|
string="Polygon (GeoJSON)",
|
|
tracking=True,
|
|
help="GeoJSON Polygon geometry. Coordinates order: [longitude, latitude].",
|
|
)
|
|
polygon_map_preview = fields.Html(
|
|
string="Polygon Map Preview",
|
|
compute="_compute_polygon_map_preview",
|
|
sanitize=False,
|
|
)
|
|
state = fields.Selection(
|
|
selection=[
|
|
("draft", "Draft"),
|
|
("active", "Active"),
|
|
("archived", "Archived"),
|
|
],
|
|
default="draft",
|
|
tracking=True,
|
|
group_expand="_group_expand_states",
|
|
)
|
|
active = fields.Boolean(default=True, tracking=True)
|
|
|
|
def _group_expand_states(self, states, domain, order):
|
|
return [key for key, _label in self._fields["state"].selection]
|
|
|
|
def action_set_active(self):
|
|
for rec in self:
|
|
if not rec.polygon_geojson:
|
|
raise ValidationError("Polygon is required before activating the FSM Zone.")
|
|
rec._extract_polygon_points()
|
|
self.write({"state": "active", "active": True})
|
|
|
|
def action_archive(self):
|
|
self.write({"state": "archived", "active": False})
|
|
|
|
def action_reset_draft(self):
|
|
self.write({"state": "draft", "active": True})
|
|
|
|
@staticmethod
|
|
def _point_in_polygon(longitude, latitude, points):
|
|
inside = False
|
|
j = len(points) - 1
|
|
for i, (xi, yi) in enumerate(points):
|
|
xj, yj = points[j]
|
|
intersects = ((yi > latitude) != (yj > latitude)) and (
|
|
longitude < (xj - xi) * (latitude - yi) / ((yj - yi) or 1e-12) + xi
|
|
)
|
|
if intersects:
|
|
inside = not inside
|
|
j = i
|
|
return inside
|
|
|
|
def _extract_polygon_points(self):
|
|
self.ensure_one()
|
|
if not self.polygon_geojson:
|
|
raise ValidationError("Polygon is required.")
|
|
try:
|
|
data = json.loads(self.polygon_geojson or "")
|
|
except json.JSONDecodeError as exc:
|
|
raise ValidationError(f"Invalid GeoJSON: {exc.msg}") from exc
|
|
|
|
ring = []
|
|
if isinstance(data, dict):
|
|
if data.get("type") == "Feature":
|
|
data = data.get("geometry") or {}
|
|
if data.get("type") != "Polygon":
|
|
raise ValidationError("GeoJSON must be of type Polygon.")
|
|
coords = data.get("coordinates") or []
|
|
if not coords or not isinstance(coords[0], list):
|
|
raise ValidationError("Polygon coordinates are missing.")
|
|
ring = coords[0]
|
|
elif isinstance(data, list):
|
|
ring = data
|
|
else:
|
|
raise ValidationError("Polygon must be a GeoJSON object or an array of points.")
|
|
|
|
points = []
|
|
for pair in ring:
|
|
if not isinstance(pair, (list, tuple)) or len(pair) < 2:
|
|
raise ValidationError("Each polygon point must be [longitude, latitude].")
|
|
points.append((float(pair[0]), float(pair[1])))
|
|
|
|
if len(points) >= 2 and points[0] == points[-1]:
|
|
points = points[:-1]
|
|
if len(points) < 3:
|
|
raise ValidationError("Polygon must contain at least 3 points.")
|
|
return points
|
|
|
|
def contains_point(self, latitude, longitude):
|
|
self.ensure_one()
|
|
if latitude is None or longitude is None:
|
|
return False
|
|
if not self.polygon_geojson:
|
|
return False
|
|
points = self._extract_polygon_points()
|
|
return self._point_in_polygon(float(longitude), float(latitude), points)
|
|
|
|
def action_open_polygon_in_map(self):
|
|
self.ensure_one()
|
|
encoded = quote(self.polygon_geojson or '{"type":"Polygon","coordinates":[[]]}')
|
|
return {
|
|
"type": "ir.actions.act_url",
|
|
"url": f"https://geojson.io/#data=data:application/json,{encoded}",
|
|
"target": "new",
|
|
}
|
|
|
|
def action_open_point_picker_map(self):
|
|
return {
|
|
"type": "ir.actions.act_url",
|
|
"url": "https://www.openstreetmap.org",
|
|
"target": "new",
|
|
}
|
|
|
|
@api.depends("polygon_geojson")
|
|
def _compute_polygon_map_preview(self):
|
|
for rec in self:
|
|
if not rec.polygon_geojson:
|
|
rec.polygon_map_preview = "<div>No polygon yet.</div>"
|
|
continue
|
|
encoded = quote(rec.polygon_geojson)
|
|
rec.polygon_map_preview = (
|
|
f'<iframe src="https://geojson.io/#data=data:application/json,{encoded}" '
|
|
'style="width:100%;height:420px;border:1px solid #d9d9d9;border-radius:6px;"></iframe>'
|
|
)
|
|
|
|
@api.constrains("polygon_geojson")
|
|
def _check_polygon_geojson(self):
|
|
for rec in self:
|
|
if rec.polygon_geojson:
|
|
rec._extract_polygon_points()
|