Add polygon FSM zones and auto zone detection by coordinates
This commit is contained in:
@@ -3,6 +3,9 @@
|
||||
<record id="repair_fsm_zone_atlanta" model="repair.fsm.zone">
|
||||
<field name="name">Atlanta Metro</field>
|
||||
<field name="code">ATL</field>
|
||||
<field name="polygon_geojson"><![CDATA[
|
||||
{"type":"Polygon","coordinates":[[[-84.55,33.60],[-84.20,33.60],[-84.20,33.90],[-84.55,33.90],[-84.55,33.60]]]}
|
||||
]]></field>
|
||||
<field name="state">active</field>
|
||||
<field name="active">True</field>
|
||||
</record>
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
from odoo import fields, models
|
||||
import json
|
||||
from urllib.parse import quote
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class RepairFsmZone(models.Model):
|
||||
@@ -9,6 +13,12 @@ class RepairFsmZone(models.Model):
|
||||
|
||||
name = fields.Char(required=True, tracking=True)
|
||||
code = fields.Char(tracking=True)
|
||||
polygon_geojson = fields.Text(
|
||||
string="Polygon (GeoJSON)",
|
||||
required=True,
|
||||
tracking=True,
|
||||
help="GeoJSON Polygon geometry. Coordinates order: [longitude, latitude].",
|
||||
)
|
||||
state = fields.Selection(
|
||||
selection=[
|
||||
("draft", "Draft"),
|
||||
@@ -32,3 +42,79 @@ class RepairFsmZone(models.Model):
|
||||
|
||||
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()
|
||||
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
|
||||
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.constrains("polygon_geojson")
|
||||
def _check_polygon_geojson(self):
|
||||
for rec in self:
|
||||
rec._extract_polygon_points()
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
<button name="action_set_active" type="object" string="Set Active" class="btn-primary" invisible="state == 'active'"/>
|
||||
<button name="action_archive" type="object" string="Archive" invisible="state == 'archived'"/>
|
||||
<button name="action_reset_draft" type="object" string="Reset to Draft" invisible="state == 'draft'"/>
|
||||
<button name="action_open_polygon_in_map" type="object" string="Open Polygon Map" invisible="not polygon_geojson"/>
|
||||
<button name="action_open_point_picker_map" type="object" string="Open OSM"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,active,archived" options="{'clickable': '1'}"/>
|
||||
</header>
|
||||
<sheet>
|
||||
@@ -17,6 +19,9 @@
|
||||
<field name="code"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="polygon_geojson" widget="text" placeholder='{"type":"Polygon","coordinates":[[[-84.55,33.60],[-84.20,33.60],[-84.20,33.90],[-84.55,33.90],[-84.55,33.60]]]}'/>
|
||||
</group>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<field name="name"/>
|
||||
<field name="code" optional="show"/>
|
||||
<field name="state" widget="badge" optional="show"/>
|
||||
<field name="polygon_geojson" optional="hide"/>
|
||||
<field name="active" optional="hide"/>
|
||||
</list>
|
||||
</field>
|
||||
|
||||
Reference in New Issue
Block a user