[{"server":null,"owner":null,"id":"7005dfff-cb17-45f7-a90a-cabc3ed75325","params":{"result":{"value":null,"datetime":1663744232,"status":null,"version":0},"hash":{"value":"qNZ7AjsUtd1j+7x43kj4Zg==","datetime":1663744345,"status":null,"version":0},"enabled":{"value":false,"datetime":1663744324,"status":null,"version":0},"error_text":{"value":"","datetime":1663744345,"status":null,"version":0},"type":{"value":"EgsScenario","datetime":1663744241,"status":null,"version":0},"settings":{"value":"{\r\n  \"values\": {\r\n    \"high\": 10.0,\r\n    \"low\": 0.1\r\n  },\r\n  \"server\": \"server\",\r\n  \"target_position_timeout\": \"00:01:00\",\r\n  \"undefined_position_timeout\": \"00:00:10\"\r\n}","datetime":1663744232,"status":null,"version":0},"state":{"value":"ok.normal","datetime":1663744232,"status":null,"version":0},"script":{"value":"# имя: 'Zone Controller'\r\n# описание: детектор слежения за целями в зоне\r\n# тип триггера: 'EgsScenario'\r\n# создан: 2017.04.24 15.55.51, Сельченков Н.Ю.\r\n# изменен: '2022.09.21 11.13.08', Сельченков Н.Ю.\r\n# подробности: https://redmine.integra-s.com:11000/projects/eilyacuario/wiki/Zone_Controller\r\n\r\nuse System.Math\r\n\r\nuse acuario2.utils.DateTimeExtension\r\nuse acuario2.utils.GeoPoint\r\nuse acuario2.utils.GeoSphere\r\nuse acuario2.utils.GeoPolygon\r\nuse acuario2.utils.GeoUtils\r\nuse acuario2.client.ZoneEvent as ZoneEventImpl from acuario2.types\r\nuse System.Collections.Generic.List(ZoneEvent) as ZoneEventList\r\n\r\n#use acuario2.client.Schema from acuario2.client\r\n#use (Schema.TraceZoneSettings.Value) as TraceZoneSettings\r\n\r\nconst TraceZoneSettingsSchema = TypeDef.Instance.Schemas[\"TraceZoneSettings\"].Value \r\nuse json_schema TraceZoneSettingsSchema as TraceZoneSettings\r\n\r\nconst TraceZoneDetector = typeof(TraceZoneSettings().detectors[0])\r\n\r\nuse new \r\n{ \r\n    target           = null as MoveableObject, \r\n    default_priority = Priority.DEFAULT, \r\n    detector         = null as TraceZoneDetector,\r\n    speed            = 0.0,\r\n    heading          = 0.0,\r\n    moving           = true,\r\n    too_fast         = false,\r\n    approached       = false,\r\n    point            = null as GeoPoint \r\n} as TargetContext\r\n\r\nuse System.Collections.Generic.Dictionary(string, TargetContext) as TargetContextDictionary\r\n\r\nuse new \r\n{\r\n    zone     = null as Zone,\r\n    polygon  = null as GeoPolygon, \r\n    settings = null as TraceZoneSettings,\r\n    targets  = null as TargetContextDictionary,\r\n    types    = null as string[]\r\n} as ZoneContext\r\n\r\nuse System.Collections.Generic.Dictionary(string, ZoneContext) as ZoneContextDictionary\r\n\r\nuse json_schema\r\n`\r\n{\r\n    \"type\": \"object\",\r\n    \"properties\":\r\n    {\r\n        \"values\":\r\n        {\r\n            \"type\": \"object\",\r\n            \"properties\":\r\n            {\r\n                \"high\": { \"type\": \"number\", \"exclusiveMinimum\": 1, \"default\": 10  },\r\n                \"low\":  { \"type\": \"number\", \"exclusiveMinimum\": 0, \"exclusiveMaximum\": 1, \"default\": 0.1 }\r\n            }\r\n        },\r\n        \"server\": { \"type\": \"string\", \"default\": \"server\" },\r\n        \"target_position_timeout\": { \"type\": \"string\", \"format\": \"TimeSpan\", \"default\": \"00:01:00\" },\r\n        \"undefined_position_timeout\": { \"type\": \"string\", \"format\": \"TimeSpan\", \"default\": \"00:00:10\" },        \r\n    }\r\n\r\n}\r\n` as SETTINGS\r\n\r\nlet settings = SETTINGS(this.settings)\r\n                  \r\nlet context = ZoneContextDictionary()\r\n\r\nwith settings.values do\r\n    high = check high > 1.0 else 10.0\r\n    low  = check 0.0 < low < 1.0 else 0.1\r\n\r\n################################################################################\r\n\r\nlet unix_now() = DateTimeExtension.ToUnixTime(DateTime.UtcNow)\r\n\r\nlet seconds_passed(datetime as DateTime) = unix_now() - DateTimeExtension.ToUnixTime(datetime)\r\n\r\nlet changed_in(param as Param, seconds as int) = seconds_passed(param.DateTime) <= seconds\r\n\r\nlet intersected_with(seq1, seq2) = from seq1 intersect seq2 count all > 0\r\n\r\nlet one_of(target as MoveableObject, types as string[]) = (target.GetType().Name in types) or (target.Types is intersected_with types)\r\n\r\n################################################################################\r\n\r\nlet get_target_zones(target as MoveableObject) = \r\n    from (target.trace_zones ?? empty) select graph.FindGraphObject(it) of type Zone to array\r\n\r\nlet get_trace_types(settings as TraceZoneSettings) = \r\n    let detectors = from settings.detectors where enabled \r\n    let types1    = from settings.defaults select Key \r\n    let types2    = from detectors select many types\r\n    \r\n    from types1 union types2 distinct to array\r\nend \r\n    \r\nlet get_zone_ctx(zone as Zone) = \r\n    let id = string(zone.Id)  \r\n    if id isnt in context then \r\n        let ctx      = ZoneContext()\r\n        ctx.zone     = zone\r\n        ctx.polygon  = GeoPolygon(zone.area)\r\n        ctx.settings = TraceZoneSettings(zone.trace_settings)\r\n        ctx.targets  = TargetContextDictionary() \r\n        ctx.types    = get_trace_types(ctx.settings)\r\n        context[id] = ctx\r\n    end\r\n    context[id] \r\nend\r\n\r\nlet get_default_priority(settings as TraceZoneSettings, target as MoveableObject) = \r\n    from settings.defaults where Key in target.Types select Value try first\r\n\r\nlet get_zone_detector(settings as TraceZoneSettings, target as MoveableObject) = \r\n    from settings.detectors where enabled and (target is one_of types) try first\r\n    \r\nlet fix_detector(detector as TraceZoneDetector) = with detector do\r\n    speed_limit              = check speed_limit > 0 else double.PositiveInfinity\r\n    speed_change_threshold   = check speed_change_threshold > 0 else double.PositiveInfinity\r\n    heading_change_threshold = check heading_change_threshold > 0 else double.PositiveInfinity\r\nend   \r\n    \r\nlet get_target_ctx(zone_ctx as ZoneContext, target as MoveableObject) = \r\n    with zone_ctx do\r\n        let id = string(target.Id)\r\n        if id isnt in targets then\r\n            let ctx              = TargetContext()\r\n            ctx.target           = target \r\n            ctx.default_priority = get_default_priority(zone_ctx.settings, target) \r\n            ctx.detector         = get_zone_detector(zone_ctx.settings, target) \r\n            ctx.speed            = target.speed \r\n            ctx.heading          = target.heading\r\n            ctx.point            = try GeoPoint(target.position) else GeoPoint(double.NaN)\r\n            fix_detector(ctx.detector)\r\n            targets[id] = ctx\r\n        end\r\n        targets[id]\r\nend    \r\n\r\nlet remove_target_ctx(zone_ctx as ZoneContext, target as MoveableObject) = \r\n    zone_ctx.targets.Remove(string(target.Id))\r\nend\r\n\r\n################################################################################\r\n\r\nlet trace_time = 1.0\r\nlet last_trace = 1.0\r\n\r\nlet ptzdevs = from graph.Values of type PTZDevice to array \r\n\r\nlet get_trace_time() = from ptzdevs select trace_time average\r\n\r\nlet update_trace_time_if_needed(obj as AbstractObject, changes as string[]) = \r\n    if (obj is PTZDevice) and (\"trace_time\" in changes) then \r\n        trace_time = get_trace_time()\r\n        last_trace = unix_now() \r\n    void()\r\nend\r\n\r\nif ptzdevs isnt empty then\r\n    trace_time = get_trace_time()\r\n    last_trace = unix_now() \r\n    this.RunOnUpdated(update_trace_time_if_needed)\r\nend\r\n\r\n################################################################################\r\n\r\nlet  inside(point  as GeoPoint,  polygon as GeoPolygon) = polygon.ContainsPoint(point)\r\nlet outside(sphere as GeoSphere, polygon as GeoPolygon) = not polygon.IntersectsWithSphere(sphere)\r\n\r\nlet out_of_zone(target as MoveableObject) = target.trace_zones is empty\r\n#let out_of_date(target as MoveableObject) = target[\"position\"].Status is \"outofdate\"\r\nlet out_of_date(target as MoveableObject) = \r\n    let timeout = target[\"position\"].TimePassed\r\n    switch target \r\n        when UndefinedTarget then timeout > settings.undefined_position_timeout\r\n                             else timeout > settings.target_position_timeout\r\n\r\nlet  ignored(target as MoveableObject) = target.trace_priority is \"ignore\"\r\nlet disabled(target as MoveableObject) = target.position is empty\r\nlet    muted(target as MoveableObject) = (target is out_of_date) or (target is ignored) or (target is disabled)\r\n\r\nlet get_target_info(target as MoveableObject) =\r\n    switch target \r\n        when UndefinedTarget x then x.vendor..\" \"..x.vendorID\r\n        when Ship x            then x.iMO..\" \"..x.mMSI..\" \"..x.name \r\n                               else target.name\r\n\r\nlet add_event(zone_ctx as ZoneContext, target_ctx as TargetContext, action as ZoneAction, events as ZoneEventList) = \r\n    let zone     = zone_ctx.zone \r\n    let target   = target_ctx.target\r\n    let detector = target_ctx.detector\r\n    with ZoneEventImpl() do\r\n        it.owner       = string(zone.Id)\r\n        it.server      = string(target.ServerId)\r\n        it.position    = target.position\r\n        it.detector    = string(this.Id)\r\n        it.target      = string(target.Id)\r\n        it.target_type = target.GetType().Name\r\n        it.target_info = get_target_info(target)\r\n        it.action      = action \r\n        it.alarm       = action in detector.alarm_actions \r\n        it.attention   = action in detector.attention_actions \r\n        events?.Add?(it)\r\n        it\r\nend\r\n\r\n################################################################################\r\n\r\nlet get_default_priority(target as MoveableObject) = \r\n    from get_target_zones(target) \r\n        select get_zone_ctx(it)\r\n        select get_target_ctx(it, target)\r\n        select default_priority\r\n        order by int(it)\r\n        try last\r\n\r\nlet get_high_priority_factor() = \r\n    let k  = settings.values.high\r\n    let t  = trace_time \r\n    let t0 = last_trace\r\n    1 + k * t / t0 - double.Epsilon\r\nend\r\n        \r\nlet get_low_priority_factor() = \r\n    let k  = settings.values.low\r\n    let t  = trace_time \r\n    let t0 = last_trace\r\n    t0 / ( t0 + t / k) + double.Epsilon\r\nend\r\n        \r\nlet get_priority_factor(priority as Priority) = \r\n    switch priority\r\n        when \"highest\" then double.PositiveInfinity\r\n        when \"high\"    then get_high_priority_factor()\r\n        when \"normal\"  then 1.0\r\n        when \"low\"     then get_low_priority_factor()\r\n        when \"lowest\"  then double.Epsilon\r\n        when \"ignore\"  then -double.Epsilon\r\n        else 1.0\r\n\r\nlet update_trace_order(target as MoveableObject) = \r\n    target.trace_order =  \r\n        if (target is out_of_zone) or (target is out_of_date) then -1\r\n        else\r\n            let snapshot_time     = DateTimeExtension.ToUnixTime(target[\"trace_snapshot\"].DateTime)\r\n            target.trace_priority = check \"DEFAULT\" != target.trace_priority else get_default_priority(target)\r\n            let priority_factor   = get_priority_factor(target.trace_priority)\r\n            let attention_factor  = if target.trace_attention then double.MaxValue else 1.0 \r\n            snapshot_time / priority_factor / attention_factor \r\nend\r\n\r\n################################################################################\r\n\r\nlet update_trace_zones(target as MoveableObject, events as ZoneEventList) = \r\n    let point           = try GeoPoint(target.position) else GeoPoint(double.NaN)\r\n    let allowed_context = from context where target is one_of Value.types to list\r\n    let trace_zones     = from allowed_context where point is inside Value.polygon select Key to list\r\n    let old_trace_zones = target.trace_zones ?? empty \r\n    let exited          = from old_trace_zones except trace_zones to array\r\n    let entered         = from trace_zones except old_trace_zones to array\r\n    from exited where it in context select context[it] select new { zone_ctx = it, target_ctx = get_target_ctx(it, target) } do\r\n        let sphere = GeoSphere(point, target_ctx.detector.exit_threshold) \r\n        if sphere is outside zone_ctx.polygon then\r\n            remove_target_ctx(zone_ctx, target) \r\n            add_event(zone_ctx, target_ctx, \"exit\", events)\r\n        else\r\n            trace_zones.Add(string(zone_ctx.zone.Id))\r\n    now\r\n    from entered where it in context select context[it] select new { zone_ctx = it, target_ctx = get_target_ctx(it, target) } do\r\n        add_event(zone_ctx, target_ctx, \"enter\", events)\r\n    now\r\n    target.trace_zones = trace_zones.ToArray() \r\nend\r\n\r\nlet check_speed(zone_ctx as ZoneContext, target_ctx as TargetContext, events as ZoneEventList) = \r\n    let zone     = zone_ctx.zone \r\n    let target   = target_ctx.target\r\n    let detector = target_ctx.detector\r\n    \r\n    let moving = target.speed > detector.stop_threshold\r\n    if moving isnt target_ctx.moving then \r\n        with add_event(zone_ctx, target_ctx, if moving then \"start\" else \"stop\", events) do\r\n            value_before = string(target_ctx.speed)\r\n            value_after  = string(target.speed)\r\n        end\r\n        target_ctx.moving = moving\r\n    end\r\n        \r\n    let too_fast = target.speed > detector.speed_limit\r\n    if too_fast isnt target_ctx.too_fast then \r\n        with add_event(zone_ctx, target_ctx, if too_fast then \"too_fast\" else \"not_too_fast\", events) do\r\n            value_before = string(target_ctx.speed)\r\n            value_after  = string(target.speed)\r\n        end\r\n        target_ctx.too_fast = too_fast\r\n    end\r\n    \r\n    let delta = Math.Abs(target.speed - target_ctx.speed)\r\n    if delta > detector.speed_change_threshold then \r\n        with add_event(zone_ctx, target_ctx, \"severe_speed_change\", events) do\r\n            value_before = string(target_ctx.speed)\r\n            value_after  = string(target.speed)\r\n        end\r\n    end\r\n        \r\nend\r\n\r\nlet check_heading(zone_ctx as ZoneContext, target_ctx as TargetContext, events as ZoneEventList) = \r\n    let zone     = zone_ctx.zone \r\n    let target   = target_ctx.target\r\n    let detector = target_ctx.detector\r\n    let delta    = Math.Abs(target.heading - target_ctx.heading)\r\n    if delta > detector.heading_change_threshold then \r\n        with add_event(zone_ctx, target_ctx, \"severe_heading_change\", events) do\r\n            value_before = string(target_ctx.heading)\r\n            value_after  = string(target.heading)\r\n        end\r\n    end\r\nend\r\n\r\nlet check_position(zone_ctx as ZoneContext, target_ctx as TargetContext, events as ZoneEventList) = \r\n    let zone     = zone_ctx.zone \r\n    let target   = target_ctx.target\r\n    \r\n    let dangerous(distance as double) = distance < target_ctx.detector.approach_threshold\r\n    \r\n    let position     = if (target isnt out_of_date) and (target isnt disabled) then target.position\r\n    target_ctx.point = try GeoPoint(position) else GeoPoint(double.NaN)\r\n\r\n    if target_ctx.point.Valid then\r\n        let participants = from zone_ctx.targets.Values \r\n                                where (it isnt target_ctx) \r\n                                and   (point.Valid) \r\n                                and   (point.Distance(target_ctx.point) is dangerous)  \r\n                                select target.Id \r\n                                order by it \r\n                                to array\r\n                                \r\n        let approached = participants isnt empty\r\n        if approached isnt target_ctx.approached then\r\n            with add_event(zone_ctx, target_ctx, if approached then \"dangerous_approach\" else \"no_dangerous_approach\", events) do\r\n                it.participants = string[](participants)\r\n            end\r\n            target_ctx.approached = approached\r\n        end\r\n    end\r\nend\r\n\r\n\r\n################################################################################\r\n\r\nlet get_server(obj as Object) = \r\n    let server = settings.server \r\n    try Guid(server) else try Guid(obj[server].Text) \r\n    else this.ServerId ?? Guid.Empty\r\nend\r\n\r\nlet update_target_if_needed(obj as AbstractObject, changes as string[]) = if obj is MoveableObject target then\r\n    let events = ZoneEventList()\r\n    \r\n    if (\"position\"    in changes) or\r\n       (\"trace_zones\" in changes) then update_trace_zones(target, events)\r\n       \r\n    from (target.trace_zones ?? empty) where it in context do    \r\n        let zone_ctx   = context[it] \r\n        let target_ctx = get_target_ctx(zone_ctx, target)\r\n        if \"speed\"    in changes then    check_speed(zone_ctx, target_ctx, events)\r\n        if \"heading\"  in changes then  check_heading(zone_ctx, target_ctx, events)\r\n        if \"position\" in changes then check_position(zone_ctx, target_ctx, events)\r\n    now\r\n\r\n    if (\"position\"        in changes) or\r\n       (\"trace_zones\"     in changes) or\r\n       (\"trace_attention\" in changes) or\r\n       (\"trace_priority\"  in changes) or\r\n       (\"trace_snapshot\"  in changes) or\r\n       (\"trace_order\"     in changes) then update_trace_order(target)\r\n    \r\n    if (target isnt muted) and (events isnt empty) then\r\n        if (from events any attention) then target.trace_attention = true \r\n        let server  = get_server(events[0])\r\n        let request = from events select many ExportCreate(server) to array\r\n        let socket  = this.Module.GetSocket(server) \r\n        socket.Put(request)\r\n    end\r\n       \r\n    void()\r\nend\r\n\r\nfrom graph.Values of type Zone where trace_settings isnt empty do \r\n    get_zone_ctx(it) \r\nnow\r\n\r\nfrom graph.Values of type MoveableObject do \r\n    let target = it\r\n    update_trace_zones(it, null)\r\n    from trace_zones where it in context do    \r\n        let zone_ctx   = context[it] \r\n        let target_ctx = get_target_ctx(zone_ctx, target)\r\n        check_speed   (zone_ctx, target_ctx, null)\r\n        check_heading (zone_ctx, target_ctx, null)\r\n        check_position(zone_ctx, target_ctx, null)\r\n    now\r\n    update_trace_order(it) \r\nnow\r\n\r\nthis.RunOnUpdated(update_target_if_needed)\r\n","datetime":1663744388,"status":null,"version":0},"name":{"value":"Zone Controller","datetime":1663744232,"status":null,"version":0}},"entity":"item","operation":"create"}]